refactor: remove battery manual entry fields in biker profile and add battery rental history table to rental details
This commit is contained in:
@@ -33,6 +33,9 @@ interface Rental {
|
||||
bikeModel: string;
|
||||
bikePlate: string;
|
||||
bikeBattery: number;
|
||||
batteryId?: string;
|
||||
batteryName?: string;
|
||||
batteryRent?: number;
|
||||
type: RentalType;
|
||||
status: RentalStatus;
|
||||
startDate: string;
|
||||
@@ -63,6 +66,15 @@ interface Rental {
|
||||
createdAt: string;
|
||||
acceptedAt?: string;
|
||||
activatedAt?: string;
|
||||
batteryHistory?: {
|
||||
id: string;
|
||||
batteryId: string;
|
||||
batteryName: string;
|
||||
assignedAt: string;
|
||||
returnedAt?: string;
|
||||
monthlyRent: number;
|
||||
status: 'active' | 'returned';
|
||||
}[];
|
||||
}
|
||||
|
||||
interface Bike {
|
||||
@@ -103,6 +115,23 @@ const mockHubs = [
|
||||
{ id: 'HUB-004', name: 'Mirpur Hub' },
|
||||
];
|
||||
|
||||
interface Battery {
|
||||
id: string;
|
||||
brand: string;
|
||||
model: string;
|
||||
soc: number;
|
||||
monthlyRent: number;
|
||||
status: 'available' | 'in-use' | 'maintenance';
|
||||
}
|
||||
|
||||
const mockBatteries: Battery[] = [
|
||||
{ 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 rentalSettings = {
|
||||
single: {
|
||||
Premium: { deposit: 5000, contractMonths: [1, 3, 6, 12], dailyRate: 200, weeklyRate: 1200, monthlyRate: 5000 },
|
||||
@@ -304,6 +333,8 @@ export default function RentalsPage() {
|
||||
subscriptionType: 'daily' | 'weekly' | 'monthly';
|
||||
contractMonths: number;
|
||||
bikeId: string;
|
||||
batteryId: string;
|
||||
batteryRent: number;
|
||||
startDate: string;
|
||||
hubId: string;
|
||||
depositAmount: number;
|
||||
@@ -316,12 +347,16 @@ export default function RentalsPage() {
|
||||
subscriptionType: 'daily',
|
||||
contractMonths: 0,
|
||||
bikeId: '',
|
||||
batteryId: '',
|
||||
batteryRent: 0,
|
||||
startDate: new Date().toISOString().split('T')[0],
|
||||
hubId: '',
|
||||
depositAmount: 0,
|
||||
depositPaymentMethod: 'cash',
|
||||
});
|
||||
|
||||
const availableBatteries = mockBatteries.filter(b => b.status === 'available');
|
||||
|
||||
const [showJournalPreview, setShowJournalPreview] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -439,6 +474,7 @@ export default function RentalsPage() {
|
||||
const bike = mockBikes.find(b => b.id === newRental.bikeId);
|
||||
const user = mockUsers.find(u => u.id === newRental.userId);
|
||||
const hub = mockHubs.find(h => h.id === newRental.hubId);
|
||||
const battery = mockBatteries.find(b => b.id === newRental.batteryId);
|
||||
const settings = planConditions[newRental.type]?.find(p => p.name === newRental.planConditionName) || planConditions[newRental.type]?.[0];
|
||||
|
||||
const rental: Rental = {
|
||||
@@ -450,6 +486,9 @@ export default function RentalsPage() {
|
||||
bikeModel: bike?.model || '',
|
||||
bikePlate: bike?.plate || '',
|
||||
bikeBattery: bike?.battery || 0,
|
||||
batteryId: newRental.batteryId || undefined,
|
||||
batteryName: battery ? `${battery.brand} ${battery.model}` : undefined,
|
||||
batteryRent: newRental.batteryRent || undefined,
|
||||
type: newRental.type,
|
||||
status: 'pending',
|
||||
startDate: newRental.startDate,
|
||||
@@ -473,6 +512,14 @@ export default function RentalsPage() {
|
||||
hubName: hub?.name || '',
|
||||
imagesApproved: false,
|
||||
createdAt: new Date().toISOString().split('T')[0],
|
||||
batteryHistory: newRental.batteryId ? [{
|
||||
id: `BAT-RENT-${Date.now()}`,
|
||||
batteryId: newRental.batteryId,
|
||||
batteryName: battery ? `${battery.brand} ${battery.model}` : '',
|
||||
assignedAt: new Date().toISOString().split('T')[0],
|
||||
monthlyRent: newRental.batteryRent,
|
||||
status: 'active' as const,
|
||||
}] : undefined,
|
||||
};
|
||||
|
||||
setRentals([...rentals, rental]);
|
||||
@@ -486,6 +533,8 @@ export default function RentalsPage() {
|
||||
subscriptionType: 'daily',
|
||||
contractMonths: 0,
|
||||
bikeId: '',
|
||||
batteryId: '',
|
||||
batteryRent: 0,
|
||||
evModel: '',
|
||||
startDate: new Date().toISOString().split('T')[0],
|
||||
hubId: '',
|
||||
@@ -927,26 +976,52 @@ export default function RentalsPage() {
|
||||
)}
|
||||
|
||||
{createStep === 3 && (
|
||||
<div>
|
||||
<h4 className="font-medium text-slate-700 mb-3">Step 3: Select Bike</h4>
|
||||
<select
|
||||
value={newRental.bikeId}
|
||||
onChange={(e) => setNewRental({ ...newRental, bikeId: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
>
|
||||
<option value="">Select Bike...</option>
|
||||
{availableBikes.map(bike => (
|
||||
<option key={bike.id} value={bike.id}>{bike.model} - {bike.plate}</option>
|
||||
))}
|
||||
</select>
|
||||
{selectedBike && (
|
||||
<div className="mt-3 flex items-center gap-2">
|
||||
<span className="text-sm text-slate-600">Battery:</span>
|
||||
<span className={`text-sm px-2 py-1 rounded-full ${selectedBike.battery > 70 ? 'bg-green-100 text-green-700' : selectedBike.battery > 40 ? 'bg-amber-100 text-amber-700' : 'bg-red-100 text-red-700'}`}>
|
||||
{selectedBike.battery}%
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-4">
|
||||
<h4 className="font-medium text-slate-700 mb-3">Step 3: Select Bike & Battery</h4>
|
||||
|
||||
<div>
|
||||
<label className="text-sm text-slate-600 mb-1 block">Select Bike</label>
|
||||
<select
|
||||
value={newRental.bikeId}
|
||||
onChange={(e) => setNewRental({ ...newRental, bikeId: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
>
|
||||
<option value="">Select Bike...</option>
|
||||
{availableBikes.map(bike => (
|
||||
<option key={bike.id} value={bike.id}>{bike.model} - {bike.plate}</option>
|
||||
))}
|
||||
</select>
|
||||
{selectedBike && (
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<span className="text-sm text-slate-600">Battery:</span>
|
||||
<span className={`text-sm px-2 py-1 rounded-full ${selectedBike.battery > 70 ? 'bg-green-100 text-green-700' : selectedBike.battery > 40 ? 'bg-amber-100 text-amber-700' : 'bg-red-100 text-red-700'}`}>
|
||||
{selectedBike.battery}%
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm text-slate-600 mb-1 block">Select Battery (Optional)</label>
|
||||
<select
|
||||
value={newRental.batteryId}
|
||||
onChange={(e) => {
|
||||
const bat = availableBatteries.find(b => b.id === e.target.value);
|
||||
setNewRental({ ...newRental, batteryId: e.target.value, batteryRent: bat?.monthlyRent || 0 });
|
||||
}}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||
>
|
||||
<option value="">No Battery - Use Bike's Battery</option>
|
||||
{availableBatteries.map(bat => (
|
||||
<option key={bat.id} value={bat.id}>{bat.brand} {bat.model} - SOC: {bat.soc}% - Rent: ৳{bat.monthlyRent}/month</option>
|
||||
))}
|
||||
</select>
|
||||
{newRental.batteryId && (
|
||||
<div className="mt-2 p-2 bg-amber-50 border border-amber-200 rounded-lg">
|
||||
<p className="text-sm text-amber-700">Battery Monthly Rent: <span className="font-bold">৳{newRental.batteryRent}/month</span></p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -958,7 +1033,18 @@ export default function RentalsPage() {
|
||||
<p className="text-sm text-slate-600">Deposit Amount: ৳{selectedPlan?.deposit.toLocaleString()}</p>
|
||||
<p className="text-sm text-slate-600">User: {selectedUser?.name}</p>
|
||||
<p className="text-sm text-slate-600">Bike: {selectedBike?.model} ({selectedBike?.plate})</p>
|
||||
{newRental.batteryId && (
|
||||
<p className="text-sm text-slate-600">Battery: {availableBatteries.find(b => b.id === newRental.batteryId)?.brand} {availableBatteries.find(b => b.id === newRental.batteryId)?.model} (Rent: ৳{newRental.batteryRent}/month)</p>
|
||||
)}
|
||||
<p className="text-sm text-slate-600">Hub: {mockHubs.find(h => h.id === newRental.hubId)?.name}</p>
|
||||
{newRental.batteryId && (
|
||||
<div className="mt-2 pt-2 border-t border-slate-200">
|
||||
<p className="text-sm font-medium text-emerald-700">
|
||||
Combined Monthly Rent: ৳{(newRental.subscriptionType === 'daily' ? selectedPlan?.dailyRate : newRental.subscriptionType === 'weekly' ? selectedPlan?.weeklyRate : selectedPlan?.monthlyRate) + newRental.batteryRent}/month
|
||||
<span className="text-xs text-slate-500 ml-1">(Bike: ৳{newRental.subscriptionType === 'daily' ? selectedPlan?.dailyRate : newRental.subscriptionType === 'weekly' ? selectedPlan?.weeklyRate : selectedPlan?.monthlyRate} + Battery: ৳{newRental.batteryRent})</span>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
|
||||
|
||||
Reference in New Issue
Block a user