feat: integrate battery selection and rental billing calculations into rental details view

This commit is contained in:
sazzadulalambd
2026-05-16 15:09:12 +06:00
parent 1882cfbb91
commit 21c408f828

View File

@@ -128,6 +128,7 @@ const mockRentals: Rental[] = [
dailyRate: 150, dailyRate: 150,
weeklyRate: 900, weeklyRate: 900,
monthlyRate: 3500, monthlyRate: 3500,
batteryRent: 1500,
totalPaid: 38500, totalPaid: 38500,
dueRental: 0, dueRental: 0,
pendingRent: 0, pendingRent: 0,
@@ -287,6 +288,23 @@ const mockHubs = [
{ id: 'HUB-004', name: 'Mirpur Hub' }, { id: 'HUB-004', name: 'Mirpur Hub' },
]; ];
interface BatteryOption {
id: string;
brand: string;
model: string;
soc: number;
monthlyRent: number;
status: 'available' | 'in-use';
}
const mockBatteries: BatteryOption[] = [
{ id: 'BAT-DH-001', brand: 'Galaxy', model: '72V 45Ah', soc: 85, monthlyRent: 1500, status: 'available' },
{ id: 'BAT-DH-002', brand: 'Titan', model: '72V 50Ah', soc: 92, monthlyRent: 1800, status: 'available' },
{ id: 'BAT-DH-003', brand: 'PowerMax', model: '60V 40Ah', soc: 78, monthlyRent: 1200, status: 'available' },
{ id: 'BAT-DH-004', brand: 'UltraCell', model: '72V 55Ah', soc: 88, monthlyRent: 2000, status: 'available' },
{ id: 'BAT-DH-005', brand: 'EcoVolt', model: '48V 30Ah', soc: 65, monthlyRent: 800, status: 'available' },
];
const mockDamageHistory = [ const mockDamageHistory = [
{ id: 'DMG-001', date: '2024-02-10', description: 'Minor scratch on left mirror', severity: 'minor', status: 'resolved' }, { id: 'DMG-001', date: '2024-02-10', description: 'Minor scratch on left mirror', severity: 'minor', status: 'resolved' },
{ id: 'DMG-002', date: '2024-03-05', description: 'Front fender dented', severity: 'moderate', status: 'reported' }, { id: 'DMG-002', date: '2024-03-05', description: 'Front fender dented', severity: 'moderate', status: 'reported' },
@@ -334,6 +352,8 @@ export default function RentalDetailPage() {
const [documents, setDocuments] = useState(mockDocuments); const [documents, setDocuments] = useState(mockDocuments);
const [showUploadModal, setShowUploadModal] = useState(false); const [showUploadModal, setShowUploadModal] = useState(false);
const [uploadDocName, setUploadDocName] = useState(''); const [uploadDocName, setUploadDocName] = useState('');
const [showAddBatteryModal, setShowAddBatteryModal] = useState(false);
const [selectedBatteryId, setSelectedBatteryId] = useState('');
const [acceptPermission, setAcceptPermission] = useState(false); const [acceptPermission, setAcceptPermission] = useState(false);
const [rejectPermission, setRejectPermission] = useState(false); const [rejectPermission, setRejectPermission] = useState(false);
@@ -486,6 +506,31 @@ export default function RentalDetailPage() {
setRental(prev => prev ? { ...prev, status: 'active', activatedAt: new Date().toISOString().split('T')[0] } : null); setRental(prev => prev ? { ...prev, status: 'active', activatedAt: new Date().toISOString().split('T')[0] } : null);
}; };
const handleAddBattery = () => {
if (!selectedBatteryId || !rental) return;
const battery = mockBatteries.find(b => b.id === selectedBatteryId);
if (!battery) return;
const newBatteryHistory: BatteryRentalHistory = {
id: `BAT-RENT-${Date.now()}`,
batteryId: battery.id,
batteryName: `${battery.brand} ${battery.model}`,
assignedAt: new Date().toISOString().split('T')[0],
monthlyRent: battery.monthlyRent,
status: 'active',
};
setRental(prev => prev ? {
...prev,
batteryId: battery.id,
batteryName: `${battery.brand} ${battery.model}`,
batteryRent: (prev.batteryRent || 0) + battery.monthlyRent,
batteryHistory: [...(prev.batteryHistory || []), newBatteryHistory],
} : null);
setShowAddBatteryModal(false);
setSelectedBatteryId('');
};
const handleAddNote = () => { const handleAddNote = () => {
if (!newNote.trim()) return; if (!newNote.trim()) return;
setNotes(prev => [...prev, { id: `n${Date.now()}`, text: newNote, createdAt: new Date().toISOString().split('T')[0] }]); setNotes(prev => [...prev, { id: `n${Date.now()}`, text: newNote, createdAt: new Date().toISOString().split('T')[0] }]);
@@ -702,12 +747,36 @@ export default function RentalDetailPage() {
<div className="space-y-2"> <div className="space-y-2">
<div className="flex justify-between"><span className="text-sm text-slate-600">Type</span><span className="text-sm font-medium text-slate-800 capitalize">{rental.subscriptionType}</span></div> <div className="flex justify-between"><span className="text-sm text-slate-600">Type</span><span className="text-sm font-medium text-slate-800 capitalize">{rental.subscriptionType}</span></div>
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-sm text-slate-600">Rate</span> <span className="text-sm text-slate-600">Bike Rate</span>
<span className="text-sm font-medium text-slate-800"> <span className="text-sm font-medium text-slate-800">
{rental.subscriptionType === 'daily' ? rental.dailyRate : rental.subscriptionType === 'weekly' ? rental.weeklyRate : rental.monthlyRate}/ {rental.subscriptionType === 'daily' ? rental.dailyRate : rental.subscriptionType === 'weekly' ? rental.weeklyRate : rental.monthlyRate}/
{rental.subscriptionType === 'daily' ? 'day' : rental.subscriptionType === 'weekly' ? 'week' : 'month'} {rental.subscriptionType === 'daily' ? 'day' : rental.subscriptionType === 'weekly' ? 'week' : 'month'}
</span> </span>
</div> </div>
{rental.batteryRent && rental.batteryRent > 0 && (
<>
<div className="flex justify-between">
<span className="text-sm text-amber-600">Battery Rate</span>
<span className="text-sm font-medium text-amber-700">
{Math.round(rental.batteryRent / (rental.subscriptionType === 'daily' ? 30 : rental.subscriptionType === 'weekly' ? 4 : 1))}/
{rental.subscriptionType === 'daily' ? 'day' : rental.subscriptionType === 'weekly' ? 'week' : 'month'}
</span>
</div>
<div className="pt-2 mt-2 border-t border-slate-100">
<div className="flex justify-between">
<span className="text-sm font-medium text-emerald-700">Total (Bike + Battery)</span>
<span className="text-sm font-bold text-emerald-700">
{rental.subscriptionType === 'daily'
? rental.dailyRate + Math.round(rental.batteryRent / 30)
: rental.subscriptionType === 'weekly'
? rental.weeklyRate + Math.round(rental.batteryRent / 4)
: rental.monthlyRate + rental.batteryRent}/
{rental.subscriptionType === 'daily' ? 'day' : rental.subscriptionType === 'weekly' ? 'week' : 'month'}
</span>
</div>
</div>
</>
)}
</div> </div>
</div> </div>
@@ -729,11 +798,16 @@ export default function RentalDetailPage() {
</div> </div>
{/* Battery Rental History */} {/* Battery Rental History */}
{rental.batteryHistory && rental.batteryHistory.length > 0 && ( <div className="bg-white p-4 rounded-xl border border-slate-200">
<div className="bg-white p-4 rounded-xl border border-slate-200"> <div className="flex items-center justify-between mb-3">
<h3 className="font-semibold text-slate-700 mb-3 flex items-center gap-2"> <h3 className="font-semibold text-slate-700 flex items-center gap-2">
<Battery className="w-5 h-5 text-amber-500" /> Battery Rental History <Battery className="w-5 h-5 text-amber-500" /> Battery Rental History
</h3> </h3>
<button onClick={() => setShowAddBatteryModal(true)} className="px-3 py-1.5 bg-amber-600 text-white rounded-lg text-xs font-medium hover:bg-amber-700 flex items-center gap-1">
<Plus className="w-3 h-3" /> Add Battery
</button>
</div>
{rental.batteryHistory && rental.batteryHistory.length > 0 ? (
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full"> <table className="w-full">
<thead className="bg-slate-50"> <thead className="bg-slate-50">
@@ -764,14 +838,80 @@ export default function RentalDetailPage() {
</tbody> </tbody>
</table> </table>
</div> </div>
{rental.batteryHistory.some(b => b.status === 'active') && ( ) : (
<div className="mt-3 p-3 bg-amber-50 border border-amber-200 rounded-lg"> <p className="text-sm text-slate-500">No battery assigned yet.</p>
<p className="text-sm text-amber-700"> )}
<span className="font-medium">Active Battery Rent: </span> {rental.batteryHistory?.some(b => b.status === 'active') && (
{rental.batteryHistory.filter(b => b.status === 'active').reduce((sum, b) => sum + b.monthlyRent, 0)}/month <div className="mt-3 p-3 bg-amber-50 border border-amber-200 rounded-lg">
</p> <p className="text-sm text-amber-700">
<span className="font-medium">Active Battery Rent: </span>
{rental.batteryHistory.filter(b => b.status === 'active').reduce((sum, b) => sum + b.monthlyRent, 0)}/month
</p>
</div>
)}
</div>
{/* Add Battery Modal */}
{showAddBatteryModal && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-xl shadow-xl w-full max-w-md">
<div className="p-4 border-b border-slate-100 flex justify-between items-center">
<h3 className="font-semibold text-slate-800">Add Battery to Rental</h3>
<button onClick={() => setShowAddBatteryModal(false)} className="text-slate-400 hover:text-slate-600">
<X className="w-5 h-5" />
</button>
</div> </div>
)} <div className="p-4 space-y-4">
<div>
<label className="text-sm 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 Battery...</option>
{mockBatteries.map(bat => (
<option key={bat.id} value={bat.id}>{bat.brand} {bat.model} - SOC: {bat.soc}% - Rent: {bat.monthlyRent}/month</option>
))}
</select>
</div>
{selectedBatteryId && (() => {
const batteryMonthlyRent = mockBatteries.find(b => b.id === selectedBatteryId)?.monthlyRent || 0;
return (
<div className="p-3 bg-amber-50 border border-amber-200 rounded-lg space-y-2">
<p className="text-sm text-amber-700">
Battery Monthly Rent: <span className="font-bold">{batteryMonthlyRent}/month</span>
</p>
<div className="text-xs text-amber-600 pt-2 border-t border-amber-100">
<p className="font-medium mb-1">Calculated Rate by Subscription:</p>
<p> Daily: {Math.round(batteryMonthlyRent / 30)}/day ({batteryMonthlyRent}/30)</p>
<p> Weekly: {Math.round(batteryMonthlyRent / 4)}/week ({batteryMonthlyRent}/4)</p>
<p> Monthly (30 days): {batteryMonthlyRent}/month</p>
<p> Monthly (31 days): {batteryMonthlyRent}/month</p>
</div>
<div className="pt-2 mt-2 border-t border-amber-100">
<p className="text-xs font-medium text-amber-700">Your Current Subscription: {rental.subscriptionType}</p>
<p className="text-sm font-bold text-amber-800">
You will pay: {rental.subscriptionType === 'daily'
? Math.round(batteryMonthlyRent / 30)
: rental.subscriptionType === 'weekly'
? Math.round(batteryMonthlyRent / 4)
: batteryMonthlyRent}/{rental.subscriptionType}
</p>
</div>
</div>
);
})()}
<div className="flex gap-2 pt-2">
<button onClick={() => setShowAddBatteryModal(false)} className="flex-1 py-2 px-4 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">
Cancel
</button>
<button onClick={handleAddBattery} disabled={!selectedBatteryId} className="flex-1 py-2 px-4 bg-amber-600 text-white rounded-lg text-sm hover:bg-amber-700 disabled:opacity-50">
Add Battery
</button>
</div>
</div>
</div>
</div> </div>
)} )}