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))); -
-
- - setAssignBatteryForm({ ...assignBatteryForm, investorShare: Number(e.target.value) })} - className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" - /> -
-
- - setAssignBatteryForm({ ...assignBatteryForm, investedAmount: Number(e.target.value) })} - className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" - /> -
-
+ 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; + })); -
-
- - setAssignBatteryForm({ ...assignBatteryForm, deposit: Number(e.target.value) })} - className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" - /> -
-
- - setAssignBatteryForm({ ...assignBatteryForm, rentPrice: Number(e.target.value) })} - className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" - /> -
-
-
+ 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)} + 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; + })); -
-
- - setAssignForm({ ...assignForm, deposit: Number(e.target.value) })} - className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" - /> -
-
- - setAssignForm({ ...assignForm, rentPrice: Number(e.target.value) })} - className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" - /> -
-
- -
-
- - setAssignForm({ ...assignForm, investorShare: Number(e.target.value) })} - className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" - /> -
-
- - setAssignForm({ ...assignForm, investedAmount: Number(e.target.value) })} - className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" - /> -
-
-
- -
- - -
-
-
- )} + 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)} + 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; + })); -
- - -
-
-
- )} + 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
+ )} +
+ )} +
+ )} +
+ +
+ + +
+
+
+ ); +} 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
+ )} +
+ )} +
+ )} +
+ +
+ + +
+
+
+ ); +} 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})? +

+ +
+ + +
+
+
+
+ ); +}