feat: add asset management modals for assigning bikes and batteries and confirming unassignments

This commit is contained in:
sazzadulalambd
2026-05-19 19:27:03 +06:00
parent b1dd4b0683
commit 123ba98c9e
5 changed files with 836 additions and 301 deletions

View File

@@ -12,6 +12,10 @@ import {
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { investors as initialInvestors, bikes as initialBikes, transactions as initialTransactions } from '@/data/mockData'; 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 }> }) { export default function InvestmentDetailPage({ params }: { params: Promise<{ id: string; investmentId: string }> }) {
const resolvedParams = use(params); const resolvedParams = use(params);
const { id: investorId, investmentId } = resolvedParams; const { id: investorId, investmentId } = resolvedParams;
@@ -291,6 +295,19 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
const [showAddBikeModal, setShowAddBikeModal] = useState(false); const [showAddBikeModal, setShowAddBikeModal] = useState(false);
const [showRegisterBikeModal, setShowRegisterBikeModal] = useState(false); const [showRegisterBikeModal, setShowRegisterBikeModal] = useState(false);
const [showAssignBatteryModal, setShowAssignBatteryModal] = 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 [showRegisterBatteryModal, setShowRegisterBatteryModal] = useState(false);
const [selectedBikeId, setSelectedBikeId] = useState(''); const [selectedBikeId, setSelectedBikeId] = useState('');
@@ -1024,9 +1041,13 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (confirm(`Are you sure you want to unassign battery ${bat.serialNumber}?`)) { setUnassignConfirmModal({
handleUnassignBattery(bat.id); 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" 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) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (confirm(`Are you sure you want to unassign bike ${bike.model} (${bike.plateNumber})?`)) { setUnassignConfirmModal({
handleUnassignBike(bike.id); 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" 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:
</div> </div>
</div> </div>
{showAddBikeModal && ( <AssignBikeModal
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"> isOpen={showAddBikeModal}
<div className="bg-white rounded-xl shadow-xl w-full max-w-md"> onClose={() => setShowAddBikeModal(false)}
<div className="p-5 border-b border-slate-100 flex items-center justify-between"> investor={investor}
<h3 className="text-lg font-bold text-slate-800 flex items-center gap-2"> bikes={bikes}
<Bike className="w-5 h-5" /> Assign Bike to Investment preselectedPlanId={investmentId}
</h3> onAssign={(planId, bikeIds) => {
<button onClick={() => setShowAddBikeModal(false)} className="p-1 hover:bg-slate-100 rounded-lg"> const assignedBikesList: any[] = [];
<X className="w-5 h-5 text-slate-500" /> setBikes(prev => prev.map(b => {
</button> if (bikeIds.includes(b.id)) {
</div> assignedBikesList.push(b);
<div className="p-5 space-y-4"> return {
<div> ...b,
<label className="block text-sm font-medium text-slate-700 mb-1">Select Bike</label> investorId: investorId,
<select investorName: investor.name,
value={selectedBikeId} investmentId: planId,
onChange={(e) => setSelectedBikeId(e.target.value)} status: 'rented',
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-investor" totalEarnings: b.totalEarnings || 0
> };
<option value="">Choose a bike...</option> }
{bikes.filter((b: any) => !b.investorId).map((bike: any) => ( return b;
<option key={bike.id} value={bike.id}>{bike.model} - {bike.plateNumber}</option> }));
))}
</select> setInvestors(prev => prev.map(inv => {
</div> if (inv.id === investor.id) {
</div> return {
<div className="p-5 border-t border-slate-100 flex justify-end gap-3"> ...inv,
<button onClick={() => setShowAddBikeModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50"> investments: inv.investments?.map((item: any) => {
Cancel if (item.id === planId) {
</button> const currentBikeIds = item.bikeIds || [];
<button const uniqueNewBikeIds = Array.from(new Set([...currentBikeIds, ...bikeIds]));
onClick={handleAssignBike} return {
disabled={!selectedBikeId} ...item,
className="px-4 py-2 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark disabled:opacity-50" bikeIds: uniqueNewBikeIds
> };
Assign Bike }
</button> return item;
</div> })
</div> };
</div> }
)} return inv;
}));
const bikeNames = assignedBikesList.map(b => `${b.model} (${b.plateNumber})`).join(', ');
toast.success(`Successfully assigned ${bikeIds.length} bike(s): ${bikeNames}`);
}}
/>
{showRegisterBikeModal && ( {showRegisterBikeModal && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"> <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
@@ -1433,83 +1464,74 @@ export default function InvestmentDetailPage({ params }: { params: Promise<{ id:
</div> </div>
)} )}
{showAssignBatteryModal && ( <AssignBatteryModal
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"> isOpen={showAssignBatteryModal}
<div className="bg-white rounded-xl shadow-xl w-full max-w-md"> onClose={() => setShowAssignBatteryModal(false)}
<div className="p-5 border-b border-slate-100 flex items-center justify-between"> investor={investor}
<h2 className="text-lg font-bold text-slate-800">Assign Battery to Investment</h2> batteries={batteries}
<button onClick={() => setShowAssignBatteryModal(false)} className="p-2 hover:bg-slate-100 rounded-lg"> unassignedBatteries={unassignedBatteries}
<X className="w-5 h-5 text-slate-400" /> preselectedPlanId={investmentId}
</button> onAssign={(planId, batteryIds) => {
</div> 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;
}));
<div className="p-5 space-y-4"> setUnassignedBatteries(prev => prev.filter(b => !batteryIds.includes(b.id)));
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Select Battery</label>
<select
value={selectedBatteryId}
onChange={(e) => setSelectedBatteryId(e.target.value)}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
>
<option value="">Select a battery</option>
{unassignedBatteries.map(bat => (
<option key={bat.id} value={bat.id}>
{bat.brand} {bat.model} - SN: {bat.serialNumber} ({bat.purchasePrice?.toLocaleString() || 0})
</option>
))}
</select>
</div>
<div className="grid grid-cols-2 gap-3"> setInvestors(prev => prev.map(inv => {
<div> if (inv.id === investor.id) {
<label className="text-sm font-medium text-slate-600 mb-1 block">Co-ownership Share (%)</label> return {
<input ...inv,
type="number" investments: inv.investments?.map((item: any) => {
value={assignBatteryForm.investorShare} if (item.id === planId) {
onChange={(e) => setAssignBatteryForm({ ...assignBatteryForm, investorShare: Number(e.target.value) })} const currentBatteryIds = item.batteryIds || [];
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" const uniqueNewBatteryIds = Array.from(new Set([...currentBatteryIds, ...batteryIds]));
/> return {
</div> ...item,
<div> batteryIds: uniqueNewBatteryIds
<label className="text-sm font-medium text-slate-600 mb-1 block">Invested Amount ()</label> };
<input }
type="number" return item;
value={assignBatteryForm.investedAmount} })
onChange={(e) => setAssignBatteryForm({ ...assignBatteryForm, investedAmount: Number(e.target.value) })} };
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" }
/> return inv;
</div> }));
</div>
<div className="grid grid-cols-2 gap-3"> const batteryNames = assignedBatteriesList.map(b => `${b.brand} ${b.model}`).join(', ');
<div> toast.success(`Successfully assigned ${batteryIds.length} battery/ies: ${batteryNames}`);
<label className="text-sm font-medium text-slate-600 mb-1 block">Security Deposit ()</label> }}
<input
type="number"
value={assignBatteryForm.deposit}
onChange={(e) => setAssignBatteryForm({ ...assignBatteryForm, deposit: 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={assignBatteryForm.rentPrice}
onChange={(e) => setAssignBatteryForm({ ...assignBatteryForm, rentPrice: Number(e.target.value) })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
/>
</div>
</div>
</div>
<div className="p-5 border-t border-slate-100 flex justify-end gap-3"> <UnassignConfirmModal
<button onClick={() => setShowAssignBatteryModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">Cancel</button> isOpen={unassignConfirmModal.show}
<button onClick={handleAssignBattery} disabled={!selectedBatteryId} className="px-4 py-2 bg-emerald-600 text-white rounded-lg text-sm hover:bg-emerald-700 disabled:opacity-50">Assign Battery</button> onClose={() => setUnassignConfirmModal(prev => ({ ...prev, show: false }))}
</div> type={unassignConfirmModal.type}
</div> name={unassignConfirmModal.name}
</div> details={unassignConfirmModal.details}
)} onConfirm={() => {
if (unassignConfirmModal.type === 'bike') {
handleUnassignBike(unassignConfirmModal.id);
} else {
handleUnassignBattery(unassignConfirmModal.id);
}
}}
/>
{showRegisterBatteryModal && ( {showRegisterBatteryModal && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"> <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">

View File

@@ -14,6 +14,10 @@ import {
ShieldCheck, Building2, Users, Check, AlertOctagon, Activity, Award, Camera, History, Settings ShieldCheck, Building2, Users, Check, AlertOctagon, Activity, Award, Camera, History, Settings
} from 'lucide-react'; } from 'lucide-react';
import AssignBikeModal from '../components/AssignBikeModal';
import AssignBatteryModal from '../components/AssignBatteryModal';
import UnassignConfirmModal from '../components/UnassignConfirmModal';
const statusColors: Record<string, string> = { const statusColors: Record<string, string> = {
active: 'bg-green-100 text-green-700', active: 'bg-green-100 text-green-700',
pending: 'bg-amber-100 text-amber-700', pending: 'bg-amber-100 text-amber-700',
@@ -139,7 +143,42 @@ export default function InvestorDetailPage() {
const [showEditModal, setShowEditModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false);
const [showAssignBikeModal, setShowAssignBikeModal] = useState(false); const [showAssignBikeModal, setShowAssignBikeModal] = useState(false);
const [selectedBikeId, setSelectedBikeId] = useState(''); const [selectedBikeId, setSelectedBikeId] = useState('');
const [selectedBikeIds, setSelectedBikeIds] = useState<string[]>([]);
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 [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({ const [registerBikeForm, setRegisterBikeForm] = useState({
plateNumber: '', plateNumber: '',
brand: 'Etron', brand: 'Etron',
@@ -400,6 +439,8 @@ export default function InvestorDetailPage() {
}, [batteries, investorId]); }, [batteries, investorId]);
const [showAssignBatteryModal, setShowAssignBatteryModal] = useState(false); const [showAssignBatteryModal, setShowAssignBatteryModal] = useState(false);
const [selectedBatteryIds, setSelectedBatteryIds] = useState<string[]>([]);
const [selectedBatteryPlanId, setSelectedBatteryPlanId] = useState('');
const [showRegisterBatteryModal, setShowRegisterBatteryModal] = useState(false); const [showRegisterBatteryModal, setShowRegisterBatteryModal] = useState(false);
const [showEditBatteryModal, setShowEditBatteryModal] = 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 availableBikesForAssignment = bikes.filter(b => !b.investorId && b.status === 'available');
const handleAssignBike = () => { const handleAssignBike = () => {
if (!selectedBikeId) { if (!selectedBikePlanId) {
toast.error('Please select a bike'); toast.error('Please select an investment plan');
return;
}
if (selectedBikeIds.length === 0) {
toast.error('Please select at least one bike');
return; 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 => { setBikes(prev => prev.map(b => {
if (b.id === selectedBikeId) { if (selectedBikeIds.includes(b.id)) {
assignedBikesList.push(b);
return { return {
...b, ...b,
investorId: investorId, investorId: investorId,
investorName: investor.name, investorName: investor.name,
investmentId: evInv?.id || 'ip1', investmentId: selectedBikePlanId,
status: 'rented', status: 'rented',
totalEarnings: b.totalEarnings || 0 totalEarnings: b.totalEarnings || 0
}; };
@@ -521,9 +563,32 @@ export default function InvestorDetailPage() {
return b; 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); setShowAssignBikeModal(false);
setSelectedBikeId(''); setSelectedBikeIds([]);
setSelectedBikePlanId('');
}; };
const handleRegisterAndAssignBike = () => { const handleRegisterAndAssignBike = () => {
@@ -676,29 +741,62 @@ export default function InvestorDetailPage() {
}; };
const handleAssignBattery = () => { const handleAssignBattery = () => {
if (!assignForm.batteryId) { if (!selectedBatteryPlanId) {
toast.error('Please select a battery'); toast.error('Please select an investment plan');
return;
}
if (selectedBatteryIds.length === 0) {
toast.error('Please select at least one battery');
return; return;
} }
const batteryToAssign = unassignedBatteries.find(b => b.id === assignForm.batteryId);
if (!batteryToAssign) return;
const assignedObjList: any[] = [];
const chosenBatteries = unassignedBatteries.filter(b => selectedBatteryIds.includes(b.id));
chosenBatteries.forEach(batteryToAssign => {
const assignedObj = { const assignedObj = {
...batteryToAssign, ...batteryToAssign,
investorId: investorId, investorId: investorId,
investorName: investor.name, investorName: investor.name,
investorSharePercentage: Number(assignForm.investorShare), investorSharePercentage: Number(assignForm.investorShare),
investedAmount: Number(assignForm.investedAmount), investedAmount: Number(assignForm.investedAmount),
investmentId: assignForm.investmentId, investmentId: selectedBatteryPlanId,
deposit: Number(assignForm.deposit), deposit: Number(assignForm.deposit),
rentPrice: Number(assignForm.rentPrice), rentPrice: Number(assignForm.rentPrice),
status: 'in-use' status: 'in-use'
}; };
assignedObjList.push(assignedObj);
});
setBatteries(prev => [...prev, assignedObj]); setBatteries(prev => [...prev, ...assignedObjList]);
setUnassignedBatteries(prev => prev.filter(b => b.id !== assignForm.batteryId)); 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); setShowAssignBatteryModal(false);
toast.success('Battery assigned to investment plan!'); setSelectedBatteryIds([]);
setSelectedBatteryPlanId('');
}; };
const handleRegisterAndAssignBattery = () => { const handleRegisterAndAssignBattery = () => {
@@ -1519,7 +1617,8 @@ export default function InvestorDetailPage() {
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<button <button
onClick={() => { onClick={() => {
setSelectedBikeId(''); setSelectedBikeIds([]);
setSelectedBikePlanId('');
setShowAssignBikeModal(true); 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" 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"
@@ -1626,9 +1725,13 @@ export default function InvestorDetailPage() {
</Link> </Link>
<button <button
onClick={() => { onClick={() => {
if (confirm(`Are you sure you want to unassign bike ${bike.model} (${bike.plateNumber})?`)) { setUnassignConfirmModal({
handleUnassignBike(bike.id); show: true,
} type: 'bike',
id: bike.id,
name: bike.model,
details: bike.plateNumber
});
}} }}
className="text-xs text-red-600 hover:text-red-800 font-semibold transition-colors px-2 py-1 bg-red-50 rounded" className="text-xs text-red-600 hover:text-red-800 font-semibold transition-colors px-2 py-1 bg-red-50 rounded"
> >
@@ -1674,6 +1777,8 @@ export default function InvestorDetailPage() {
investorShare: 100, investorShare: 100,
investedAmount: 45000 investedAmount: 45000
}); });
setSelectedBatteryIds([]);
setSelectedBatteryPlanId('');
setShowAssignBatteryModal(true); setShowAssignBatteryModal(true);
}} }}
className="px-4 py-2 bg-emerald-50 text-emerald-700 border border-emerald-200 rounded-lg text-sm font-semibold hover:bg-emerald-100 flex items-center gap-2 transition-colors" className="px-4 py-2 bg-emerald-50 text-emerald-700 border border-emerald-200 rounded-lg text-sm font-semibold hover:bg-emerald-100 flex items-center gap-2 transition-colors"
@@ -1793,7 +1898,15 @@ export default function InvestorDetailPage() {
<Edit className="w-3 h-3" /> Edit <Edit className="w-3 h-3" /> Edit
</button> </button>
<button <button
onClick={() => handleUnassignBattery(battery.id)} onClick={() => {
setUnassignConfirmModal({
show: true,
type: 'battery',
id: battery.id,
name: `${battery.brand} ${battery.model}`,
details: battery.serialNumber
});
}}
className="px-2 py-1 text-xs font-bold text-red-600 hover:bg-red-50 rounded transition-all flex items-center gap-0.5" className="px-2 py-1 text-xs font-bold text-red-600 hover:bg-red-50 rounded transition-all flex items-center gap-0.5"
title="Unassign Battery" title="Unassign Battery"
> >
@@ -2948,122 +3061,58 @@ export default function InvestorDetailPage() {
</div> </div>
)} )}
{showAssignBatteryModal && ( <AssignBatteryModal
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"> isOpen={showAssignBatteryModal}
<div className="bg-white rounded-xl shadow-xl w-full max-w-md overflow-hidden flex flex-col"> onClose={() => setShowAssignBatteryModal(false)}
<div className="p-5 border-b border-emerald-100 bg-emerald-50 flex items-center justify-between"> investor={investor}
<h2 className="text-lg font-bold text-emerald-800 flex items-center gap-2"> batteries={batteries}
<Battery className="w-5 h-5 text-emerald-600 animate-bounce" /> unassignedBatteries={unassignedBatteries}
Assign Battery to Partner onAssign={(planId, batteryIds) => {
</h2> const assignedBatteriesList: any[] = [];
<button onClick={() => setShowAssignBatteryModal(false)} className="p-2 hover:bg-emerald-100 rounded-lg text-emerald-600"> setBatteries(prev => prev.map(b => {
<X className="w-5 h-5" /> if (batteryIds.includes(b.id)) {
</button> assignedBatteriesList.push(b);
</div> 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;
}));
<div className="p-5 space-y-4 overflow-y-auto max-h-[75vh]"> setUnassignedBatteries(prev => prev.filter(b => !batteryIds.includes(b.id)));
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Select Unassigned Battery *</label> setInvestors(prev => prev.map(inv => {
<select if (inv.id === investor.id) {
value={assignForm.batteryId} return {
onChange={(e) => { ...inv,
const selected = unassignedBatteries.find(b => b.id === e.target.value); investments: inv.investments?.map((item: any) => {
setAssignForm({ if (item.id === planId) {
...assignForm, const currentBatteryIds = item.batteryIds || [];
batteryId: e.target.value, const uniqueNewBatteryIds = Array.from(new Set([...currentBatteryIds, ...batteryIds]));
deposit: selected?.deposit || 5000, return {
rentPrice: selected?.rentPrice || 150, ...item,
investedAmount: selected?.purchasePrice || 45000 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}`);
}} }}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
>
<option value="">Choose a battery pack</option>
{unassignedBatteries.map(bat => (
<option key={bat.id} value={bat.id}>
{bat.brand} {bat.model} ({bat.serialNumber})
</option>
))}
</select>
</div>
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Link to Investment Plan *</label>
<select
value={assignForm.investmentId}
onChange={(e) => setAssignForm({ ...assignForm, 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?.map((inv: any) => (
<option key={inv.id} value={inv.id}>
{inv.planName} (#{inv.id?.slice(-6)})
</option>
))}
</select>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Refundable Deposit (৳) *</label>
<input
type="number"
value={assignForm.deposit}
onChange={(e) => setAssignForm({ ...assignForm, deposit: 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 Price (৳) *</label>
<input
type="number"
value={assignForm.rentPrice}
onChange={(e) => setAssignForm({ ...assignForm, rentPrice: Number(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-4">
<div>
<label className="text-sm font-medium text-slate-600 mb-1 block">Investor Share % *</label>
<input
type="number"
value={assignForm.investorShare}
onChange={(e) => setAssignForm({ ...assignForm, investorShare: 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">Contribution (৳) *</label>
<input
type="number"
value={assignForm.investedAmount}
onChange={(e) => setAssignForm({ ...assignForm, investedAmount: Number(e.target.value) })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
/>
</div>
</div>
</div>
<div className="p-5 border-t border-slate-100 flex justify-end gap-3">
<button
onClick={() => 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
</button>
<button
onClick={handleAssignBattery}
disabled={!assignForm.batteryId || !assignForm.investmentId}
className="px-4 py-2 bg-emerald-600 text-white rounded-lg text-sm font-semibold hover:bg-emerald-700 disabled:opacity-50 disabled:cursor-not-allowed shadow-sm"
>
Assign Battery
</button>
</div>
</div>
</div>
)}
{showRegisterBatteryModal && ( {showRegisterBatteryModal && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"> <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
@@ -3340,42 +3389,67 @@ export default function InvestorDetailPage() {
</div> </div>
)} )}
{showAssignBikeModal && ( <AssignBikeModal
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"> isOpen={showAssignBikeModal}
<div className="bg-white rounded-xl shadow-xl w-full max-w-md"> onClose={() => setShowAssignBikeModal(false)}
<div className="p-5 border-b border-slate-100 flex items-center justify-between"> investor={investor}
<h2 className="text-lg font-bold text-investor flex items-center gap-2"> bikes={bikes}
<Bike className="w-5 h-5 text-investor animate-bounce" /> onAssign={(planId, bikeIds) => {
Assign Bike to Investor const assignedBikesList: any[] = [];
</h2> setBikes(prev => prev.map(b => {
<button onClick={() => setShowAssignBikeModal(false)} className="p-2 hover:bg-slate-100 rounded-lg"> if (bikeIds.includes(b.id)) {
<X className="w-5 h-5 text-slate-400" /> assignedBikesList.push(b);
</button> return {
</div> ...b,
investorId: investorId,
investorName: investor.name,
investmentId: planId,
status: 'rented',
totalEarnings: b.totalEarnings || 0
};
}
return b;
}));
<div className="p-5"> setInvestors(prev => prev.map(inv => {
<label className="text-sm font-medium text-slate-600 mb-1 block">Select Bike</label> if (inv.id === investor.id) {
<select return {
value={selectedBikeId} ...inv,
onChange={(e) => setSelectedBikeId(e.target.value)} investments: inv.investments?.map((item: any) => {
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" if (item.id === planId) {
> const currentBikeIds = item.bikeIds || [];
<option value="">Select a bike</option> const uniqueNewBikeIds = Array.from(new Set([...currentBikeIds, ...bikeIds]));
{availableBikesForAssignment.map(bike => ( return {
<option key={bike.id} value={bike.id}> ...item,
{bike.model} - {bike.plateNumber} (৳{bike.purchasePrice?.toLocaleString() || 0}) bikeIds: uniqueNewBikeIds
</option> };
))} }
</select> return item;
</div> })
};
}
return inv;
}));
<div className="p-5 border-t border-slate-100 flex justify-end gap-3"> const bikeNames = assignedBikesList.map(b => `${b.model} (${b.plateNumber})`).join(', ');
<button onClick={() => setShowAssignBikeModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">Cancel</button> toast.success(`Successfully assigned ${bikeIds.length} bike(s): ${bikeNames}`);
<button onClick={handleAssignBike} disabled={!selectedBikeId} className="px-4 py-2 bg-investor text-white rounded-lg text-sm hover:bg-investor-dark disabled:opacity-50">Assign Bike</button> }}
</div> />
</div>
</div> <UnassignConfirmModal
)} isOpen={unassignConfirmModal.show}
onClose={() => 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 && ( {showRegisterBikeModal && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"> <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">

View File

@@ -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<string[]>([]);
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 (
<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 overflow-hidden flex flex-col">
<div className="p-5 border-b border-emerald-100 bg-emerald-50 flex items-center justify-between">
<h2 className="text-lg font-bold text-emerald-800 flex items-center gap-2">
<Battery className="w-5 h-5 text-emerald-600 animate-bounce" />
Assign Battery to Partner
</h2>
<button onClick={onClose} className="p-2 hover:bg-emerald-100 rounded-lg text-emerald-600">
<X className="w-5 h-5" />
</button>
</div>
<div className="p-5 space-y-4 overflow-y-auto max-h-[70vh]">
<div>
<label className="text-sm font-medium text-slate-600 mb-1.5 block">Link to Investment Plan *</label>
<select
value={selectedPlanId}
disabled={!!preselectedPlanId}
onChange={(e) => {
setSelectedPlanId(e.target.value);
setSelectedBatteryIds([]);
}}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:border-emerald-500 disabled:bg-slate-50 disabled:text-slate-500"
>
<option value="">Select plan</option>
{investor.investments?.filter((inv: any) => inv.assetType === 'battery' || inv.planName.toLowerCase().includes('battery')).map((inv: any) => {
const curAssigned = batteries.filter(b => b.investmentId === inv.id).length;
const target = getPlanTargetAssetCount(inv);
const rem = Math.max(0, target - curAssigned);
return (
<option key={inv.id} value={inv.id}>
{inv.planName} (Remaining: {rem} / {target} Pack{target !== 1 ? 's' : ''})
</option>
);
})}
</select>
</div>
{selectedPlanId && (
<div className="space-y-2">
<div className="flex justify-between items-center">
<label className="text-sm font-semibold text-slate-700 block">Select Battery Pack(s) *</label>
<span className="text-xs font-bold text-emerald-700 bg-emerald-100 px-2 py-0.5 rounded-full">
Selected: {selectedBatteryIds.length} / {remainingCapacity}
</span>
</div>
{remainingCapacity === 0 ? (
<div className="p-3 text-center text-xs text-amber-700 bg-amber-50 rounded-lg border border-amber-200">
This plan has reached its full capacity of {targetCount} battery pack(s). Unassign some batteries first to assign new ones.
</div>
) : (
<div className="border border-slate-200 rounded-lg max-h-56 overflow-y-auto divide-y divide-slate-100">
{unassignedBatteries.map(bat => {
const isChecked = selectedBatteryIds.includes(bat.id);
const isDisabled = !isChecked && selectedBatteryIds.length >= remainingCapacity;
return (
<label
key={bat.id}
className={`flex items-center justify-between p-3 cursor-pointer text-sm transition-colors ${
isChecked ? 'bg-emerald-50/50' : isDisabled ? 'opacity-50 cursor-not-allowed bg-slate-50' : 'hover:bg-slate-50'
}`}
>
<div className="flex items-center gap-2.5">
<input
type="checkbox"
checked={isChecked}
disabled={isDisabled}
onChange={(e) => {
if (e.target.checked) {
if (selectedBatteryIds.length < remainingCapacity) {
setSelectedBatteryIds([...selectedBatteryIds, bat.id]);
} else {
toast.error(`Cannot select more than ${remainingCapacity} batteries`);
}
} else {
setSelectedBatteryIds(selectedBatteryIds.filter(id => id !== bat.id));
}
}}
className="rounded text-emerald-600 focus:ring-emerald-500 border-slate-300 w-4 h-4"
/>
<div>
<p className="font-semibold text-slate-800">{bat.brand} {bat.model}</p>
<p className="text-xs text-slate-500">SN: {bat.serialNumber}</p>
</div>
</div>
<span className="text-slate-600 font-medium text-xs">{bat.purchasePrice?.toLocaleString() || 0}</span>
</label>
);
})}
{unassignedBatteries.length === 0 && (
<div className="p-4 text-center text-slate-400 text-sm">No unassigned batteries available</div>
)}
</div>
)}
</div>
)}
</div>
<div className="p-5 border-t border-slate-100 flex justify-end gap-3 bg-slate-50">
<button
onClick={onClose}
className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm font-semibold hover:bg-slate-100 bg-white"
>
Cancel
</button>
<button
onClick={handleAssignSubmit}
disabled={!selectedPlanId || selectedBatteryIds.length === 0}
className="px-4 py-2 bg-emerald-600 text-white rounded-lg text-sm font-semibold hover:bg-emerald-700 disabled:opacity-50 disabled:cursor-not-allowed shadow-sm transition-colors"
>
Assign {selectedBatteryIds.length > 0 ? `${selectedBatteryIds.length} Battery/ies` : 'Battery'}
</button>
</div>
</div>
</div>
);
}

View File

@@ -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<string[]>([]);
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 (
<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 overflow-hidden flex flex-col">
<div className="p-5 border-b border-slate-100 flex items-center justify-between">
<h2 className="text-lg font-bold text-investor flex items-center gap-2">
<Bike className="w-5 h-5 text-investor animate-bounce" />
Assign Bike to Investor
</h2>
<button onClick={onClose} 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 overflow-y-auto max-h-[70vh]">
<div>
<label className="text-sm font-medium text-slate-600 mb-1.5 block">Select Investment Plan *</label>
<select
value={selectedPlanId}
disabled={!!preselectedPlanId}
onChange={(e) => {
setSelectedPlanId(e.target.value);
setSelectedBikeIds([]);
}}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:border-investor disabled:bg-slate-50 disabled:text-slate-500"
>
<option value="">Choose an active plan</option>
{investor.investments?.filter((inv: any) => inv.assetType === 'bike' || !inv.assetType || inv.planName.toLowerCase().includes('ev') || inv.planName.toLowerCase().includes('bike')).map((inv: any) => {
const curAssigned = bikes.filter(b => b.investmentId === inv.id).length;
const target = getPlanTargetAssetCount(inv);
const rem = Math.max(0, target - curAssigned);
return (
<option key={inv.id} value={inv.id}>
{inv.planName} (Remaining: {rem} / {target} Bike{target !== 1 ? 's' : ''})
</option>
);
})}
</select>
</div>
{selectedPlanId && (
<div className="space-y-2">
<div className="flex justify-between items-center">
<label className="text-sm font-semibold text-slate-700 block">Select Bike(s) *</label>
<span className="text-xs font-bold text-investor bg-investor/10 px-2 py-0.5 rounded-full">
Selected: {selectedBikeIds.length} / {remainingCapacity}
</span>
</div>
{remainingCapacity === 0 ? (
<div className="p-3 text-center text-xs text-amber-700 bg-amber-50 rounded-lg border border-amber-200">
This plan has reached its full capacity of {targetCount} bike(s). Unassign some bikes first to assign new ones.
</div>
) : (
<div className="border border-slate-200 rounded-lg max-h-48 overflow-y-auto divide-y divide-slate-100">
{availableBikes.map(bike => {
const isChecked = selectedBikeIds.includes(bike.id);
const isDisabled = !isChecked && selectedBikeIds.length >= remainingCapacity;
return (
<label
key={bike.id}
className={`flex items-center justify-between p-3 cursor-pointer text-sm transition-colors ${
isChecked ? 'bg-investor/5' : isDisabled ? 'opacity-50 cursor-not-allowed bg-slate-50' : 'hover:bg-slate-50'
}`}
>
<div className="flex items-center gap-2.5">
<input
type="checkbox"
checked={isChecked}
disabled={isDisabled}
onChange={(e) => {
if (e.target.checked) {
if (selectedBikeIds.length < remainingCapacity) {
setSelectedBikeIds([...selectedBikeIds, bike.id]);
} else {
toast.error(`Cannot select more than ${remainingCapacity} bikes`);
}
} else {
setSelectedBikeIds(selectedBikeIds.filter(id => id !== bike.id));
}
}}
className="rounded text-investor focus:ring-investor border-slate-300 w-4 h-4"
/>
<div>
<p className="font-semibold text-slate-800">{bike.model} {bike.brand}</p>
<p className="text-xs text-slate-500">{bike.plateNumber}</p>
</div>
</div>
<span className="text-slate-600 font-medium text-xs">{bike.purchasePrice?.toLocaleString() || 0}</span>
</label>
);
})}
{availableBikes.length === 0 && (
<div className="p-4 text-center text-slate-400 text-sm">No unassigned available bikes found</div>
)}
</div>
)}
</div>
)}
</div>
<div className="p-5 border-t border-slate-100 flex justify-end gap-3 bg-slate-50">
<button
onClick={onClose}
className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm font-semibold hover:bg-slate-100 bg-white"
>
Cancel
</button>
<button
onClick={handleAssignSubmit}
disabled={!selectedPlanId || selectedBikeIds.length === 0}
className="px-4 py-2 bg-investor text-white rounded-lg text-sm font-semibold hover:bg-investor-dark disabled:opacity-50 disabled:cursor-not-allowed shadow-sm transition-colors"
>
Assign {selectedBikeIds.length > 0 ? `${selectedBikeIds.length} Bike(s)` : 'Bike'}
</button>
</div>
</div>
</div>
);
}

View File

@@ -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 (
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4 animate-in fade-in zoom-in-95 duration-200">
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-md overflow-hidden border border-slate-100">
<div className="p-6 text-center">
<div className="w-16 h-16 bg-red-50 rounded-full flex items-center justify-center mx-auto mb-4 border border-red-100">
<AlertTriangle className="w-8 h-8 text-red-600 animate-pulse" />
</div>
<h3 className="text-lg font-bold text-slate-900 mb-2">Unassign Confirmation</h3>
<p className="text-sm text-slate-500 mb-6 px-1">
Are you sure you want to unassign {type} <span className="font-semibold text-slate-800">{name}</span> ({details})?
</p>
<div className="flex gap-3 justify-center">
<button
onClick={onClose}
className="px-5 py-2.5 border border-slate-200 text-slate-600 rounded-xl text-sm font-semibold hover:bg-slate-50 transition-colors flex-1"
>
Cancel
</button>
<button
onClick={() => {
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
</button>
</div>
</div>
</div>
</div>
);
}