feat: integrate battery selection and rental billing calculations into rental details view
This commit is contained in:
@@ -128,6 +128,7 @@ const mockRentals: Rental[] = [
|
||||
dailyRate: 150,
|
||||
weeklyRate: 900,
|
||||
monthlyRate: 3500,
|
||||
batteryRent: 1500,
|
||||
totalPaid: 38500,
|
||||
dueRental: 0,
|
||||
pendingRent: 0,
|
||||
@@ -287,6 +288,23 @@ const mockHubs = [
|
||||
{ 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 = [
|
||||
{ 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' },
|
||||
@@ -334,6 +352,8 @@ export default function RentalDetailPage() {
|
||||
const [documents, setDocuments] = useState(mockDocuments);
|
||||
const [showUploadModal, setShowUploadModal] = useState(false);
|
||||
const [uploadDocName, setUploadDocName] = useState('');
|
||||
const [showAddBatteryModal, setShowAddBatteryModal] = useState(false);
|
||||
const [selectedBatteryId, setSelectedBatteryId] = useState('');
|
||||
|
||||
const [acceptPermission, setAcceptPermission] = 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);
|
||||
};
|
||||
|
||||
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 = () => {
|
||||
if (!newNote.trim()) return;
|
||||
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="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">Rate</span>
|
||||
<span className="text-sm text-slate-600">Bike Rate</span>
|
||||
<span className="text-sm font-medium text-slate-800">
|
||||
৳{rental.subscriptionType === 'daily' ? rental.dailyRate : rental.subscriptionType === 'weekly' ? rental.weeklyRate : rental.monthlyRate}/
|
||||
{rental.subscriptionType === 'daily' ? 'day' : rental.subscriptionType === 'weekly' ? 'week' : 'month'}
|
||||
</span>
|
||||
</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>
|
||||
|
||||
@@ -729,11 +798,16 @@ export default function RentalDetailPage() {
|
||||
</div>
|
||||
|
||||
{/* Battery Rental History */}
|
||||
{rental.batteryHistory && rental.batteryHistory.length > 0 && (
|
||||
<div className="bg-white p-4 rounded-xl border border-slate-200">
|
||||
<h3 className="font-semibold text-slate-700 mb-3 flex items-center gap-2">
|
||||
<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 flex items-center gap-2">
|
||||
<Battery className="w-5 h-5 text-amber-500" /> Battery Rental History
|
||||
</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">
|
||||
<table className="w-full">
|
||||
<thead className="bg-slate-50">
|
||||
@@ -764,14 +838,80 @@ export default function RentalDetailPage() {
|
||||
</tbody>
|
||||
</table>
|
||||
</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-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>
|
||||
) : (
|
||||
<p className="text-sm text-slate-500">No battery assigned yet.</p>
|
||||
)}
|
||||
{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-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 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>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user