diff --git a/src/app/admin/investors/[id]/investments/[investmentId]/page.tsx b/src/app/admin/investors/[id]/investments/[investmentId]/page.tsx
index f21bffa..b17bac2 100644
--- a/src/app/admin/investors/[id]/investments/[investmentId]/page.tsx
+++ b/src/app/admin/investors/[id]/investments/[investmentId]/page.tsx
@@ -12,6 +12,10 @@ import {
import toast from 'react-hot-toast';
import { investors as initialInvestors, bikes as initialBikes, transactions as initialTransactions } from '@/data/mockData';
+import AssignBikeModal from '../../../components/AssignBikeModal';
+import AssignBatteryModal from '../../../components/AssignBatteryModal';
+import UnassignConfirmModal from '../../../components/UnassignConfirmModal';
+
export default function InvestmentDetailPage({ params }: { params: Promise<{ id: string; investmentId: string }> }) {
const resolvedParams = use(params);
const { id: investorId, investmentId } = resolvedParams;
@@ -291,6 +295,19 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
const [showAddBikeModal, setShowAddBikeModal] = useState(false);
const [showRegisterBikeModal, setShowRegisterBikeModal] = useState(false);
const [showAssignBatteryModal, setShowAssignBatteryModal] = useState(false);
+ const [unassignConfirmModal, setUnassignConfirmModal] = useState<{
+ show: boolean;
+ type: 'bike' | 'battery';
+ id: string;
+ name: string;
+ details: string;
+ }>({
+ show: false,
+ type: 'bike',
+ id: '',
+ name: '',
+ details: ''
+ });
const [showRegisterBatteryModal, setShowRegisterBatteryModal] = useState(false);
const [selectedBikeId, setSelectedBikeId] = useState('');
@@ -1024,9 +1041,13 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
- if (confirm(`Are you sure you want to unassign battery ${bat.serialNumber}?`)) {
- handleUnassignBattery(bat.id);
- }
+ setUnassignConfirmModal({
+ show: true,
+ type: 'battery',
+ id: bat.id,
+ name: `${bat.brand} ${bat.model}`,
+ details: bat.serialNumber
+ });
}}
className="p-2 hover:bg-red-50 rounded-lg text-slate-400 hover:text-red-600 transition-colors"
>
@@ -1169,9 +1190,13 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
- if (confirm(`Are you sure you want to unassign bike ${bike.model} (${bike.plateNumber})?`)) {
- handleUnassignBike(bike.id);
- }
+ setUnassignConfirmModal({
+ show: true,
+ type: 'bike',
+ id: bike.id,
+ name: bike.model,
+ details: bike.plateNumber
+ });
}}
className="p-2 hover:bg-red-50 rounded-lg text-slate-400 hover:text-red-600 transition-colors"
>
@@ -1295,47 +1320,53 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
- {showAddBikeModal && (
-
-
-
-
- Assign Bike to Investment
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
+ setShowAddBikeModal(false)}
+ investor={investor}
+ bikes={bikes}
+ preselectedPlanId={investmentId}
+ onAssign={(planId, bikeIds) => {
+ const assignedBikesList: any[] = [];
+ setBikes(prev => prev.map(b => {
+ if (bikeIds.includes(b.id)) {
+ assignedBikesList.push(b);
+ return {
+ ...b,
+ investorId: investorId,
+ investorName: investor.name,
+ investmentId: planId,
+ status: 'rented',
+ totalEarnings: b.totalEarnings || 0
+ };
+ }
+ return b;
+ }));
+
+ setInvestors(prev => prev.map(inv => {
+ if (inv.id === investor.id) {
+ return {
+ ...inv,
+ investments: inv.investments?.map((item: any) => {
+ if (item.id === planId) {
+ const currentBikeIds = item.bikeIds || [];
+ const uniqueNewBikeIds = Array.from(new Set([...currentBikeIds, ...bikeIds]));
+ return {
+ ...item,
+ bikeIds: uniqueNewBikeIds
+ };
+ }
+ return item;
+ })
+ };
+ }
+ return inv;
+ }));
+
+ const bikeNames = assignedBikesList.map(b => `${b.model} (${b.plateNumber})`).join(', ');
+ toast.success(`Successfully assigned ${bikeIds.length} bike(s): ${bikeNames}`);
+ }}
+ />
{showRegisterBikeModal && (
@@ -1433,83 +1464,74 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
)}
- {showAssignBatteryModal && (
-
-
-
-
Assign Battery to Investment
-
-
+
setShowAssignBatteryModal(false)}
+ investor={investor}
+ batteries={batteries}
+ unassignedBatteries={unassignedBatteries}
+ preselectedPlanId={investmentId}
+ onAssign={(planId, batteryIds) => {
+ const assignedBatteriesList: any[] = [];
+ setBatteries(prev => prev.map(b => {
+ if (batteryIds.includes(b.id)) {
+ assignedBatteriesList.push(b);
+ return {
+ ...b,
+ investorId: investorId,
+ investorName: investor.name,
+ investmentId: planId,
+ status: 'in-use',
+ investedAmount: b.purchasePrice || 45000,
+ investorSharePercentage: 100,
+ deposit: b.deposit || 5000,
+ rentPrice: b.rentPrice || 150
+ };
+ }
+ return b;
+ }));
-
-
-
-
-
+ setUnassignedBatteries(prev => prev.filter(b => !batteryIds.includes(b.id)));
-
+ setInvestors(prev => prev.map(inv => {
+ if (inv.id === investor.id) {
+ return {
+ ...inv,
+ investments: inv.investments?.map((item: any) => {
+ if (item.id === planId) {
+ const currentBatteryIds = item.batteryIds || [];
+ const uniqueNewBatteryIds = Array.from(new Set([...currentBatteryIds, ...batteryIds]));
+ return {
+ ...item,
+ batteryIds: uniqueNewBatteryIds
+ };
+ }
+ return item;
+ })
+ };
+ }
+ return inv;
+ }));
-
-
+ const batteryNames = assignedBatteriesList.map(b => `${b.brand} ${b.model}`).join(', ');
+ toast.success(`Successfully assigned ${batteryIds.length} battery/ies: ${batteryNames}`);
+ }}
+ />
-
-
-
-
-
-
- )}
+ setUnassignConfirmModal(prev => ({ ...prev, show: false }))}
+ type={unassignConfirmModal.type}
+ name={unassignConfirmModal.name}
+ details={unassignConfirmModal.details}
+ onConfirm={() => {
+ if (unassignConfirmModal.type === 'bike') {
+ handleUnassignBike(unassignConfirmModal.id);
+ } else {
+ handleUnassignBattery(unassignConfirmModal.id);
+ }
+ }}
+ />
{showRegisterBatteryModal && (
diff --git a/src/app/admin/investors/[id]/page.tsx b/src/app/admin/investors/[id]/page.tsx
index e135218..16b5ed4 100644
--- a/src/app/admin/investors/[id]/page.tsx
+++ b/src/app/admin/investors/[id]/page.tsx
@@ -14,6 +14,10 @@ import {
ShieldCheck, Building2, Users, Check, AlertOctagon, Activity, Award, Camera, History, Settings
} from 'lucide-react';
+import AssignBikeModal from '../components/AssignBikeModal';
+import AssignBatteryModal from '../components/AssignBatteryModal';
+import UnassignConfirmModal from '../components/UnassignConfirmModal';
+
const statusColors: Record
= {
active: 'bg-green-100 text-green-700',
pending: 'bg-amber-100 text-amber-700',
@@ -139,7 +143,42 @@ export default function InvestorDetailPage() {
const [showEditModal, setShowEditModal] = useState(false);
const [showAssignBikeModal, setShowAssignBikeModal] = useState(false);
const [selectedBikeId, setSelectedBikeId] = useState('');
+ const [selectedBikeIds, setSelectedBikeIds] = useState([]);
+ const [selectedBikePlanId, setSelectedBikePlanId] = useState('');
+
+ const getPlanTargetAssetCount = (plan: any) => {
+ if (plan.assetType === 'battery' || plan.planName?.toLowerCase().includes('battery')) {
+ if (plan.id === 'ip3') return 2;
+ const nameLower = plan.planName?.toLowerCase() || '';
+ if (nameLower.includes('10')) return 10;
+ if (nameLower.includes('5')) return 5;
+ if (nameLower.includes('1')) return 1;
+ return 1;
+ } else {
+ if (plan.id === 'ip1') return 1;
+ if (plan.id === 'ip2') return 1;
+ const nameLower = plan.planName?.toLowerCase() || '';
+ if (nameLower.includes('10')) return 10;
+ if (nameLower.includes('5')) return 5;
+ if (nameLower.includes('1')) return 1;
+ return 1;
+ }
+ };
+
const [showRegisterBikeModal, setShowRegisterBikeModal] = useState(false);
+ const [unassignConfirmModal, setUnassignConfirmModal] = useState<{
+ show: boolean;
+ type: 'bike' | 'battery';
+ id: string;
+ name: string;
+ details: string;
+ }>({
+ show: false,
+ type: 'bike',
+ id: '',
+ name: '',
+ details: ''
+ });
const [registerBikeForm, setRegisterBikeForm] = useState({
plateNumber: '',
brand: 'Etron',
@@ -400,6 +439,8 @@ export default function InvestorDetailPage() {
}, [batteries, investorId]);
const [showAssignBatteryModal, setShowAssignBatteryModal] = useState(false);
+ const [selectedBatteryIds, setSelectedBatteryIds] = useState([]);
+ const [selectedBatteryPlanId, setSelectedBatteryPlanId] = useState('');
const [showRegisterBatteryModal, setShowRegisterBatteryModal] = useState(false);
const [showEditBatteryModal, setShowEditBatteryModal] = useState(false);
@@ -497,23 +538,24 @@ export default function InvestorDetailPage() {
const availableBikesForAssignment = bikes.filter(b => !b.investorId && b.status === 'available');
const handleAssignBike = () => {
- if (!selectedBikeId) {
- toast.error('Please select a bike');
+ if (!selectedBikePlanId) {
+ toast.error('Please select an investment plan');
+ return;
+ }
+ if (selectedBikeIds.length === 0) {
+ toast.error('Please select at least one 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];
+ const assignedBikesList: any[] = [];
setBikes(prev => prev.map(b => {
- if (b.id === selectedBikeId) {
+ if (selectedBikeIds.includes(b.id)) {
+ assignedBikesList.push(b);
return {
...b,
investorId: investorId,
investorName: investor.name,
- investmentId: evInv?.id || 'ip1',
+ investmentId: selectedBikePlanId,
status: 'rented',
totalEarnings: b.totalEarnings || 0
};
@@ -521,9 +563,32 @@ export default function InvestorDetailPage() {
return b;
}));
- toast.success(`Bike ${bikeToAssign.model} (${bikeToAssign.plateNumber}) successfully assigned!`);
+ // Update investor's investments to hold the bike IDs
+ setInvestors(prev => prev.map(inv => {
+ if (inv.id === investor.id) {
+ return {
+ ...inv,
+ investments: inv.investments?.map((item: any) => {
+ if (item.id === selectedBikePlanId) {
+ const currentBikeIds = item.bikeIds || [];
+ const uniqueNewBikeIds = Array.from(new Set([...currentBikeIds, ...selectedBikeIds]));
+ return {
+ ...item,
+ bikeIds: uniqueNewBikeIds
+ };
+ }
+ return item;
+ })
+ };
+ }
+ return inv;
+ }));
+
+ const bikeNames = assignedBikesList.map(b => `${b.model} (${b.plateNumber})`).join(', ');
+ toast.success(`Successfully assigned ${selectedBikeIds.length} bike(s): ${bikeNames}`);
setShowAssignBikeModal(false);
- setSelectedBikeId('');
+ setSelectedBikeIds([]);
+ setSelectedBikePlanId('');
};
const handleRegisterAndAssignBike = () => {
@@ -676,29 +741,62 @@ export default function InvestorDetailPage() {
};
const handleAssignBattery = () => {
- if (!assignForm.batteryId) {
- toast.error('Please select a battery');
+ if (!selectedBatteryPlanId) {
+ toast.error('Please select an investment plan');
+ return;
+ }
+ if (selectedBatteryIds.length === 0) {
+ toast.error('Please select at least one battery');
return;
}
- const batteryToAssign = unassignedBatteries.find(b => b.id === assignForm.batteryId);
- if (!batteryToAssign) return;
- const assignedObj = {
- ...batteryToAssign,
- investorId: investorId,
- investorName: investor.name,
- investorSharePercentage: Number(assignForm.investorShare),
- investedAmount: Number(assignForm.investedAmount),
- investmentId: assignForm.investmentId,
- deposit: Number(assignForm.deposit),
- rentPrice: Number(assignForm.rentPrice),
- status: 'in-use'
- };
+ const assignedObjList: any[] = [];
+ const chosenBatteries = unassignedBatteries.filter(b => selectedBatteryIds.includes(b.id));
- setBatteries(prev => [...prev, assignedObj]);
- setUnassignedBatteries(prev => prev.filter(b => b.id !== assignForm.batteryId));
+ chosenBatteries.forEach(batteryToAssign => {
+ const assignedObj = {
+ ...batteryToAssign,
+ investorId: investorId,
+ investorName: investor.name,
+ investorSharePercentage: Number(assignForm.investorShare),
+ investedAmount: Number(assignForm.investedAmount),
+ investmentId: selectedBatteryPlanId,
+ deposit: Number(assignForm.deposit),
+ rentPrice: Number(assignForm.rentPrice),
+ status: 'in-use'
+ };
+ assignedObjList.push(assignedObj);
+ });
+
+ setBatteries(prev => [...prev, ...assignedObjList]);
+ setUnassignedBatteries(prev => prev.filter(b => !selectedBatteryIds.includes(b.id)));
+
+ // Update investor's investments to hold the battery IDs
+ setInvestors(prev => prev.map(inv => {
+ if (inv.id === investor.id) {
+ return {
+ ...inv,
+ investments: inv.investments?.map((item: any) => {
+ if (item.id === selectedBatteryPlanId) {
+ const currentBatteryIds = item.batteryIds || [];
+ const uniqueNewBatteryIds = Array.from(new Set([...currentBatteryIds, ...selectedBatteryIds]));
+ return {
+ ...item,
+ batteryIds: uniqueNewBatteryIds
+ };
+ }
+ return item;
+ })
+ };
+ }
+ return inv;
+ }));
+
+ const serialsList = chosenBatteries.map(b => b.serialNumber).join(', ');
+ toast.success(`Successfully assigned ${selectedBatteryIds.length} battery pack(s): ${serialsList}`);
setShowAssignBatteryModal(false);
- toast.success('Battery assigned to investment plan!');
+ setSelectedBatteryIds([]);
+ setSelectedBatteryPlanId('');
};
const handleRegisterAndAssignBattery = () => {
@@ -1519,7 +1617,8 @@ export default function InvestorDetailPage() {
)}
- {showAssignBatteryModal && (
-
-
-
-
-
- Assign Battery to Partner
-
- setShowAssignBatteryModal(false)} className="p-2 hover:bg-emerald-100 rounded-lg text-emerald-600">
-
-
-
+
setShowAssignBatteryModal(false)}
+ investor={investor}
+ batteries={batteries}
+ unassignedBatteries={unassignedBatteries}
+ onAssign={(planId, batteryIds) => {
+ const assignedBatteriesList: any[] = [];
+ setBatteries(prev => prev.map(b => {
+ if (batteryIds.includes(b.id)) {
+ assignedBatteriesList.push(b);
+ return {
+ ...b,
+ investorId: investorId,
+ investorName: investor.name,
+ investmentId: planId,
+ status: 'in-use',
+ investedAmount: b.purchasePrice || 45000,
+ investorSharePercentage: 100,
+ deposit: b.deposit || 5000,
+ rentPrice: b.rentPrice || 150
+ };
+ }
+ return b;
+ }));
-
-
-
-
-
+ setUnassignedBatteries(prev => prev.filter(b => !batteryIds.includes(b.id)));
-
-
-
-
+ setInvestors(prev => prev.map(inv => {
+ if (inv.id === investor.id) {
+ return {
+ ...inv,
+ investments: inv.investments?.map((item: any) => {
+ if (item.id === planId) {
+ const currentBatteryIds = item.batteryIds || [];
+ const uniqueNewBatteryIds = Array.from(new Set([...currentBatteryIds, ...batteryIds]));
+ return {
+ ...item,
+ batteryIds: uniqueNewBatteryIds
+ };
+ }
+ return item;
+ })
+ };
+ }
+ return inv;
+ }));
-
-
-
-
-
-
- setShowAssignBatteryModal(false)}
- className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm font-semibold hover:bg-slate-50"
- >
- Cancel
-
-
- Assign Battery
-
-
-
-
- )}
+ const batteryNames = assignedBatteriesList.map(b => `${b.brand} ${b.model}`).join(', ');
+ toast.success(`Successfully assigned ${batteryIds.length} battery/ies: ${batteryNames}`);
+ }}
+ />
{showRegisterBatteryModal && (
@@ -3340,42 +3389,67 @@ export default function InvestorDetailPage() {
)}
- {showAssignBikeModal && (
-
-
-
-
-
- Assign Bike to Investor
-
- setShowAssignBikeModal(false)} className="p-2 hover:bg-slate-100 rounded-lg">
-
-
-
+
setShowAssignBikeModal(false)}
+ investor={investor}
+ bikes={bikes}
+ onAssign={(planId, bikeIds) => {
+ const assignedBikesList: any[] = [];
+ setBikes(prev => prev.map(b => {
+ if (bikeIds.includes(b.id)) {
+ assignedBikesList.push(b);
+ return {
+ ...b,
+ investorId: investorId,
+ investorName: investor.name,
+ investmentId: planId,
+ status: 'rented',
+ totalEarnings: b.totalEarnings || 0
+ };
+ }
+ return b;
+ }));
-
-
-
-
+ setInvestors(prev => prev.map(inv => {
+ if (inv.id === investor.id) {
+ return {
+ ...inv,
+ investments: inv.investments?.map((item: any) => {
+ if (item.id === planId) {
+ const currentBikeIds = item.bikeIds || [];
+ const uniqueNewBikeIds = Array.from(new Set([...currentBikeIds, ...bikeIds]));
+ return {
+ ...item,
+ bikeIds: uniqueNewBikeIds
+ };
+ }
+ return item;
+ })
+ };
+ }
+ return inv;
+ }));
-
- setShowAssignBikeModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">Cancel
- Assign Bike
-
-
-
- )}
+ const bikeNames = assignedBikesList.map(b => `${b.model} (${b.plateNumber})`).join(', ');
+ toast.success(`Successfully assigned ${bikeIds.length} bike(s): ${bikeNames}`);
+ }}
+ />
+
+ setUnassignConfirmModal(prev => ({ ...prev, show: false }))}
+ type={unassignConfirmModal.type}
+ name={unassignConfirmModal.name}
+ details={unassignConfirmModal.details}
+ onConfirm={() => {
+ if (unassignConfirmModal.type === 'bike') {
+ handleUnassignBike(unassignConfirmModal.id);
+ } else {
+ handleUnassignBattery(unassignConfirmModal.id);
+ }
+ }}
+ />
{showRegisterBikeModal && (
diff --git a/src/app/admin/investors/components/AssignBatteryModal.tsx b/src/app/admin/investors/components/AssignBatteryModal.tsx
new file mode 100644
index 0000000..b69a1c0
--- /dev/null
+++ b/src/app/admin/investors/components/AssignBatteryModal.tsx
@@ -0,0 +1,191 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import { Battery, X } from 'lucide-react';
+import toast from 'react-hot-toast';
+
+interface AssignBatteryModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ investor: any;
+ batteries: any[];
+ unassignedBatteries: any[];
+ preselectedPlanId?: string;
+ onAssign: (planId: string, batteryIds: string[]) => void;
+}
+
+export default function AssignBatteryModal({
+ isOpen,
+ onClose,
+ investor,
+ batteries,
+ unassignedBatteries,
+ preselectedPlanId = '',
+ onAssign
+}: AssignBatteryModalProps) {
+ const [selectedPlanId, setSelectedPlanId] = useState(preselectedPlanId);
+ const [selectedBatteryIds, setSelectedBatteryIds] = useState
([]);
+
+ useEffect(() => {
+ if (isOpen) {
+ setSelectedPlanId(preselectedPlanId);
+ setSelectedBatteryIds([]);
+ }
+ }, [isOpen, preselectedPlanId]);
+
+ if (!isOpen || !investor) return null;
+
+ const getPlanTargetAssetCount = (plan: any) => {
+ if (!plan) return 1;
+ if (plan.assetType === 'battery' || plan.planName?.toLowerCase().includes('battery')) {
+ if (plan.id === 'ip3') return 2;
+ const nameLower = plan.planName?.toLowerCase() || '';
+ if (nameLower.includes('10')) return 10;
+ if (nameLower.includes('5')) return 5;
+ return 1;
+ } else {
+ if (plan.id === 'ip1' || plan.id === 'ip2') return 1;
+ const nameLower = plan.planName?.toLowerCase() || '';
+ if (nameLower.includes('10')) return 10;
+ if (nameLower.includes('5')) return 5;
+ return 1;
+ }
+ };
+
+ const selectedPlan = investor.investments?.find((inv: any) => inv.id === selectedPlanId);
+ const assignedCount = selectedPlan ? batteries.filter(b => b.investmentId === selectedPlan.id).length : 0;
+ const targetCount = selectedPlan ? getPlanTargetAssetCount(selectedPlan) : 0;
+ const remainingCapacity = selectedPlan ? Math.max(0, targetCount - assignedCount) : 0;
+
+ const handleAssignSubmit = () => {
+ if (!selectedPlanId) {
+ toast.error('Please select an investment plan');
+ return;
+ }
+ if (selectedBatteryIds.length === 0) {
+ toast.error('Please select at least one battery');
+ return;
+ }
+ onAssign(selectedPlanId, selectedBatteryIds);
+ onClose();
+ };
+
+ return (
+
+
+
+
+
+ Assign Battery to Partner
+
+
+
+
+
+
+
+
+
+
+
+
+ {selectedPlanId && (
+
+
+
+
+ Selected: {selectedBatteryIds.length} / {remainingCapacity}
+
+
+
+ {remainingCapacity === 0 ? (
+
+ This plan has reached its full capacity of {targetCount} battery pack(s). Unassign some batteries first to assign new ones.
+
+ ) : (
+
+ {unassignedBatteries.map(bat => {
+ const isChecked = selectedBatteryIds.includes(bat.id);
+ const isDisabled = !isChecked && selectedBatteryIds.length >= remainingCapacity;
+ return (
+
+ );
+ })}
+ {unassignedBatteries.length === 0 && (
+
No unassigned batteries available
+ )}
+
+ )}
+
+ )}
+
+
+
+
+ Cancel
+
+
+ Assign {selectedBatteryIds.length > 0 ? `${selectedBatteryIds.length} Battery/ies` : 'Battery'}
+
+
+
+
+ );
+}
diff --git a/src/app/admin/investors/components/AssignBikeModal.tsx b/src/app/admin/investors/components/AssignBikeModal.tsx
new file mode 100644
index 0000000..dfcb8e7
--- /dev/null
+++ b/src/app/admin/investors/components/AssignBikeModal.tsx
@@ -0,0 +1,191 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import { Bike, X } from 'lucide-react';
+import toast from 'react-hot-toast';
+
+interface AssignBikeModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ investor: any;
+ bikes: any[];
+ preselectedPlanId?: string;
+ onAssign: (planId: string, bikeIds: string[]) => void;
+}
+
+export default function AssignBikeModal({
+ isOpen,
+ onClose,
+ investor,
+ bikes,
+ preselectedPlanId = '',
+ onAssign
+}: AssignBikeModalProps) {
+ const [selectedPlanId, setSelectedPlanId] = useState(preselectedPlanId);
+ const [selectedBikeIds, setSelectedBikeIds] = useState([]);
+
+ useEffect(() => {
+ if (isOpen) {
+ setSelectedPlanId(preselectedPlanId);
+ setSelectedBikeIds([]);
+ }
+ }, [isOpen, preselectedPlanId]);
+
+ if (!isOpen || !investor) return null;
+
+ const getPlanTargetAssetCount = (plan: any) => {
+ if (!plan) return 1;
+ if (plan.assetType === 'battery' || plan.planName?.toLowerCase().includes('battery')) {
+ if (plan.id === 'ip3') return 2;
+ const nameLower = plan.planName?.toLowerCase() || '';
+ if (nameLower.includes('10')) return 10;
+ if (nameLower.includes('5')) return 5;
+ return 1;
+ } else {
+ if (plan.id === 'ip1' || plan.id === 'ip2') return 1;
+ const nameLower = plan.planName?.toLowerCase() || '';
+ if (nameLower.includes('10')) return 10;
+ if (nameLower.includes('5')) return 5;
+ return 1;
+ }
+ };
+
+ const selectedPlan = investor.investments?.find((inv: any) => inv.id === selectedPlanId);
+ const assignedCount = selectedPlan ? bikes.filter(b => b.investmentId === selectedPlan.id).length : 0;
+ const targetCount = selectedPlan ? getPlanTargetAssetCount(selectedPlan) : 0;
+ const remainingCapacity = selectedPlan ? Math.max(0, targetCount - assignedCount) : 0;
+
+ const availableBikes = bikes.filter(b => !b.investorId && b.status === 'available');
+
+ const handleAssignSubmit = () => {
+ if (!selectedPlanId) {
+ toast.error('Please select an investment plan');
+ return;
+ }
+ if (selectedBikeIds.length === 0) {
+ toast.error('Please select at least one bike');
+ return;
+ }
+ onAssign(selectedPlanId, selectedBikeIds);
+ onClose();
+ };
+
+ return (
+
+
+
+
+
+ Assign Bike to Investor
+
+
+
+
+
+
+
+
+
+
+
+
+ {selectedPlanId && (
+
+
+
+
+ Selected: {selectedBikeIds.length} / {remainingCapacity}
+
+
+
+ {remainingCapacity === 0 ? (
+
+ This plan has reached its full capacity of {targetCount} bike(s). Unassign some bikes first to assign new ones.
+
+ ) : (
+
+ {availableBikes.map(bike => {
+ const isChecked = selectedBikeIds.includes(bike.id);
+ const isDisabled = !isChecked && selectedBikeIds.length >= remainingCapacity;
+ return (
+
+ );
+ })}
+ {availableBikes.length === 0 && (
+
No unassigned available bikes found
+ )}
+
+ )}
+
+ )}
+
+
+
+
+ Cancel
+
+
+ Assign {selectedBikeIds.length > 0 ? `${selectedBikeIds.length} Bike(s)` : 'Bike'}
+
+
+
+
+ );
+}
diff --git a/src/app/admin/investors/components/UnassignConfirmModal.tsx b/src/app/admin/investors/components/UnassignConfirmModal.tsx
new file mode 100644
index 0000000..779b6f8
--- /dev/null
+++ b/src/app/admin/investors/components/UnassignConfirmModal.tsx
@@ -0,0 +1,57 @@
+'use client';
+
+import { AlertTriangle } from 'lucide-react';
+
+interface UnassignConfirmModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ type: 'bike' | 'battery';
+ name: string;
+ details: string;
+ onConfirm: () => void;
+}
+
+export default function UnassignConfirmModal({
+ isOpen,
+ onClose,
+ type,
+ name,
+ details,
+ onConfirm
+}: UnassignConfirmModalProps) {
+ if (!isOpen) return null;
+
+ return (
+
+
+
+
+
Unassign Confirmation
+
+ Are you sure you want to unassign {type} {name} ({details})?
+
+
+
+
+ Cancel
+
+ {
+ onConfirm();
+ onClose();
+ }}
+ className="px-5 py-2.5 bg-red-600 text-white rounded-xl text-sm font-semibold hover:bg-red-700 hover:shadow-lg transition-all flex-1"
+ >
+ Unassign
+
+
+
+
+
+ );
+}