feat: replace hardcoded rental conditions with dynamic plan configurations and update rental creation flow

This commit is contained in:
sazzadulalambd
2026-05-10 02:47:04 +06:00
parent 0d7b684c77
commit 2e7bf23752
2 changed files with 86 additions and 59 deletions

View File

@@ -272,12 +272,33 @@ export default function RentalsPage() {
const [editPermission, setEditPermission] = useState(false);
const [lockPermission, setLockPermission] = useState(false);
const [unlockPermission, setUnlockPermission] = useState(false);
const [planConditions, setPlanConditions] = useState<{
single: { name: string; deposit: number; dailyRate: number; weeklyRate: number; monthlyRate: number; contractMonths: number[] }[];
shared: { name: string; deposit: number; dailyRate: number; weeklyRate: number; monthlyRate: number; contractMonths: number[] }[];
'rent-to-own': { name: string; deposit: number; dailyRate: number; weeklyRate: number; monthlyRate: number; contractMonths: number[] }[];
}>({
single: [
{ name: 'Single Rent - Premium', deposit: 25000, dailyRate: 400, weeklyRate: 2800, monthlyRate: 12000, contractMonths: [1, 3, 6, 12] },
{ name: 'Single Rent - Standard', deposit: 20000, dailyRate: 300, weeklyRate: 2100, monthlyRate: 9000, contractMonths: [1, 3, 6, 12] },
{ name: 'Single Rent - Economy', deposit: 15000, dailyRate: 250, weeklyRate: 1750, monthlyRate: 7500, contractMonths: [1, 3, 6, 12] },
],
shared: [
{ name: 'Share an EV - Premium', deposit: 20000, dailyRate: 300, weeklyRate: 2100, monthlyRate: 8400, contractMonths: [1, 3, 6, 12] },
{ name: 'Share an EV - Standard', deposit: 15000, dailyRate: 200, weeklyRate: 1400, monthlyRate: 5600, contractMonths: [1, 3, 6, 12] },
{ name: 'Share an EV - Economy', deposit: 12000, dailyRate: 150, weeklyRate: 1050, monthlyRate: 4200, contractMonths: [1, 3, 6, 12] },
],
'rent-to-own': [
{ name: 'Rent to Own - Premium', deposit: 25000, dailyRate: 350, weeklyRate: 2450, monthlyRate: 10500, contractMonths: [12, 18, 24, 36] },
{ name: 'Rent to Own - Standard', deposit: 18000, dailyRate: 250, weeklyRate: 1750, monthlyRate: 7000, contractMonths: [12, 18, 24, 36] },
{ name: 'Rent to Own - Economy', deposit: 15000, dailyRate: 200, weeklyRate: 1400, monthlyRate: 6000, contractMonths: [12, 18, 24, 36] },
],
});
const [createStep, setCreateStep] = useState(1);
const [newRental, setNewRental] = useState<{
userId: string;
type: RentalType;
condition: 'Premium' | 'Standard' | 'Economy';
planConditionName: string;
subscriptionType: 'daily' | 'weekly' | 'monthly';
contractMonths: number;
bikeId: string;
@@ -287,7 +308,7 @@ export default function RentalsPage() {
}>({
userId: '',
type: 'single',
condition: 'Standard',
planConditionName: '',
subscriptionType: 'daily',
contractMonths: 0,
bikeId: '',
@@ -393,7 +414,7 @@ export default function RentalsPage() {
const eligibleUsers = mockUsers.filter(u => u.kycStatus === 'approved' && !u.hasActiveRental);
const availableBikes = mockBikes.filter(b => b.status === 'available');
const selectedSettings = rentalSettings[newRental.type]?.[newRental.condition] || rentalSettings.single.Standard;
const selectedPlan = planConditions[newRental.type]?.find(p => p.name === newRental.planConditionName) || planConditions[newRental.type]?.[0];
const stats = {
active: rentals.filter(r => r.status === 'active').length,
@@ -411,7 +432,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 settings = rentalSettings[newRental.type]?.[newRental.condition];
const settings = planConditions[newRental.type]?.find(p => p.name === newRental.planConditionName) || planConditions[newRental.type]?.[0];
const rental: Rental = {
id: `RNT-${String(rentals.length + 1).padStart(3, '0')}`,
@@ -454,7 +475,7 @@ export default function RentalsPage() {
setNewRental({
userId: '',
type: 'single',
condition: 'Standard',
planConditionName: planConditions.single[1]?.name || '',
subscriptionType: 'daily',
contractMonths: 0,
bikeId: '',
@@ -681,19 +702,33 @@ export default function RentalsPage() {
<div className="p-4 space-y-4">
{createStep === 1 && (
<div>
<h4 className="font-medium text-slate-700 mb-3">Step 1: Select User</h4>
<p className="text-sm text-slate-500 mb-3">Only KYC-approved users without active rentals are shown.</p>
<select
value={newRental.userId}
onChange={(e) => setNewRental({ ...newRental, userId: e.target.value })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
>
<option value="">Select User...</option>
{eligibleUsers.map(user => (
<option key={user.id} value={user.id}>{user.name} ({user.phone})</option>
))}
</select>
<div className="space-y-4">
<h4 className="font-medium text-slate-700 mb-3">Step 1: Start Date & Hub</h4>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-1.5 block">Start Date</label>
<input type="date" value={newRental.startDate} onChange={(e) => setNewRental({ ...newRental, startDate: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div>
<div>
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-1.5 block">Hub</label>
<select value={newRental.hubId} onChange={(e) => setNewRental({ ...newRental, hubId: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm">
<option value="">Select Hub...</option>
{mockHubs.map(hub => (
<option key={hub.id} value={hub.id}>{hub.name}</option>
))}
</select>
</div>
</div>
<div>
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-1.5 block">Select User</label>
<p className="text-xs text-slate-400 mb-2">Only KYC-approved users without active rentals are shown.</p>
<select value={newRental.userId} onChange={(e) => setNewRental({ ...newRental, userId: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm">
<option value="">Select User...</option>
{eligibleUsers.map(user => (
<option key={user.id} value={user.id}>{user.name} ({user.phone})</option>
))}
</select>
</div>
</div>
)}
@@ -703,7 +738,7 @@ export default function RentalsPage() {
<label className="text-sm text-slate-600">Rental Type</label>
<select
value={newRental.type}
onChange={(e) => setNewRental({ ...newRental, type: e.target.value as RentalType, condition: 'Standard', subscriptionType: 'daily' })}
onChange={(e) => setNewRental({ ...newRental, type: e.target.value as RentalType, planConditionName: planConditions[e.target.value as RentalType]?.[1]?.name || planConditions[e.target.value as RentalType]?.[0]?.name || '', subscriptionType: 'daily', contractMonths: 0 })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
>
<option value="single">Single Rent</option>
@@ -712,25 +747,17 @@ export default function RentalsPage() {
</select>
</div>
<div>
<label className="text-sm text-slate-600">Condition</label>
<div className="grid grid-cols-3 gap-2 mt-1">
{(['Premium', 'Standard', 'Economy'] as const).map(cond => (
<button
key={cond}
type="button"
onClick={() => setNewRental({ ...newRental, condition: cond })}
className={`py-2 px-3 rounded-lg text-sm border ${
newRental.condition === cond
? cond === 'Premium' ? 'bg-purple-100 border-purple-300 text-purple-700'
: cond === 'Standard' ? 'bg-blue-100 border-blue-300 text-blue-700'
: 'bg-amber-100 border-amber-300 text-amber-700'
: 'border-slate-200 text-slate-600 hover:bg-slate-50'
}`}
>
{cond}
</button>
<label className="text-sm text-slate-600">Plan Condition</label>
<select
value={newRental.planConditionName}
onChange={(e) => setNewRental({ ...newRental, planConditionName: e.target.value, contractMonths: 0, subscriptionType: 'daily' })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
>
<option value="">Select Plan...</option>
{planConditions[newRental.type]?.map(plan => (
<option key={plan.name} value={plan.name}>{plan.name}</option>
))}
</div>
</select>
</div>
<div>
<label className="text-sm text-slate-600">Contract Duration</label>
@@ -740,7 +767,7 @@ export default function RentalsPage() {
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1"
>
<option value={0}>Daily Basis</option>
{selectedSettings.contractMonths.map(m => (
{selectedPlan?.contractMonths.map(m => (
<option key={m} value={m}>{m} Months</option>
))}
</select>
@@ -770,11 +797,11 @@ export default function RentalsPage() {
</div>
<div className="bg-emerald-50 p-3 rounded-lg border border-emerald-100">
<div className="flex items-center justify-between mb-1">
<span className="text-xs text-emerald-600 font-medium uppercase tracking-wide">{newRental.condition} - {newRental.type === 'single' ? 'Single Rent' : newRental.type === 'shared' ? 'Share EV' : 'Rent to Own'}</span>
<span className="text-xs text-emerald-600 font-medium uppercase tracking-wide">{newRental.planConditionName}</span>
</div>
<p className="text-sm text-slate-600">Deposit: <span className="font-semibold text-slate-800">{selectedSettings.deposit.toLocaleString()}</span></p>
<p className="text-sm text-slate-600">Deposit: <span className="font-semibold text-slate-800">{selectedPlan?.deposit.toLocaleString()}</span></p>
<p className="text-sm text-slate-600">
Rate: <span className="font-semibold text-slate-800">{newRental.subscriptionType === 'daily' ? selectedSettings.dailyRate : newRental.subscriptionType === 'weekly' ? selectedSettings.weeklyRate : selectedSettings.monthlyRate}</span>
Rate: <span className="font-semibold text-slate-800">{newRental.subscriptionType === 'daily' ? selectedPlan?.dailyRate : newRental.subscriptionType === 'weekly' ? selectedPlan?.weeklyRate : selectedPlan?.monthlyRate}</span>
<span className="text-slate-500">/{newRental.subscriptionType === 'daily' ? 'day' : newRental.subscriptionType === 'weekly' ? 'week' : 'month'}</span>
</p>
</div>
@@ -837,12 +864,12 @@ export default function RentalsPage() {
<div className="space-y-4">
<h4 className="font-medium text-slate-700 mb-3">Step 5: Deposit Payment</h4>
<div className="bg-slate-50 p-3 rounded-lg">
<p className="text-sm text-slate-600">Deposit Amount: {selectedSettings.deposit.toLocaleString()}</p>
<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>
<p className="text-sm text-slate-600">Hub: {mockHubs.find(h => h.id === newRental.hubId)?.name}</p>
</div>
{selectedSettings.deposit > 0 && (
{selectedPlan.deposit > 0 && (
<div>
<label className="text-sm text-slate-600 mb-2 block">Payment Method</label>
<div className="flex gap-2">
@@ -865,7 +892,7 @@ export default function RentalsPage() {
</div>
</div>
)}
{selectedSettings.deposit > 0 && (
{selectedPlan.deposit > 0 && (
<div className="bg-blue-50 p-3 rounded-lg">
<p className="text-sm font-medium text-blue-700 mb-2">Journal Preview</p>
<table className="w-full text-xs">
@@ -883,13 +910,13 @@ export default function RentalsPage() {
{newRental.depositPaymentMethod === 'bank' && '1100 - Bank'}
{newRental.depositPaymentMethod === 'wallet' && '1200 - Biker Wallet'}
</td>
<td className="px-2 py-1 text-right">{selectedSettings.deposit}</td>
<td className="px-2 py-1 text-right">{selectedPlan.deposit}</td>
<td className="px-2 py-1 text-right">-</td>
</tr>
<tr>
<td className="px-2 py-1">2100 - Deposit Received</td>
<td className="px-2 py-1 text-right">-</td>
<td className="px-2 py-1 text-right">{selectedSettings.deposit}</td>
<td className="px-2 py-1 text-right">{selectedPlan.deposit}</td>
</tr>
</tbody>
</table>
@@ -925,7 +952,7 @@ export default function RentalsPage() {
</button>
) : (
<>
{selectedSettings.deposit > 0 && (
{selectedPlan.deposit > 0 && (
<button className="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm flex items-center gap-2">
<Printer className="w-4 h-4" /> Print Invoice
</button>

View File

@@ -2461,8 +2461,8 @@ export default function CompanySettingsPage() {
<div className="p-5 space-y-5">
<div className="grid grid-cols-3 gap-4">
<div>
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">Plan Name</label>
<input type="text" value={plan.name} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].name = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Plan Name" />
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">Plan Condition</label>
<input type="text" value={plan.name} onChange={(e) => { const updated = [...settings.plans.singleRent]; updated[idx].name = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, singleRent: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Plan Condition" />
</div>
<div>
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">EV Model Numbers</label>
@@ -2600,8 +2600,8 @@ export default function CompanySettingsPage() {
<div className="p-5 space-y-5">
<div className="grid grid-cols-3 gap-4">
<div>
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">Plan Name</label>
<input type="text" value={plan.name} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].name = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Plan Name" />
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">Plan Condition</label>
<input type="text" value={plan.name} onChange={(e) => { const updated = [...settings.plans.rentToOwn]; updated[idx].name = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, rentToOwn: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Plan Condition" />
</div>
<div>
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">EV Model Numbers</label>
@@ -2739,8 +2739,8 @@ export default function CompanySettingsPage() {
<div className="p-5 space-y-5">
<div className="grid grid-cols-3 gap-4">
<div>
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">Plan Name</label>
<input type="text" value={plan.name} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].name = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Plan Name" />
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">Plan Condition</label>
<input type="text" value={plan.name} onChange={(e) => { const updated = [...settings.plans.shareEv]; updated[idx].name = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, shareEv: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Plan Condition" />
</div>
<div>
<label className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2 block">EV Model Numbers</label>
@@ -2891,7 +2891,7 @@ export default function CompanySettingsPage() {
<div className="p-4">
<div className="grid lg:grid-cols-3 gap-4">
<div>
<label className="text-sm text-slate-600">Plan Name</label>
<label className="text-sm text-slate-600">Plan Condition</label>
<input type="text" value={newInvestName} onChange={(e) => setNewInvestName(e.target.value)} placeholder="e.g., 1 Bike Plan" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
</div>
<div>
@@ -2986,7 +2986,7 @@ export default function CompanySettingsPage() {
<div className="p-4">
<div className="grid lg:grid-cols-3 gap-4">
<div>
<label className="text-sm text-slate-600">Plan Name</label>
<label className="text-sm text-slate-600">Plan Condition</label>
<input type="text" value={plan.name} onChange={(e) => { const updated = [...settings.plans.investment]; updated[idx].name = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, investment: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
</div>
<div>
@@ -3101,7 +3101,7 @@ export default function CompanySettingsPage() {
<div className="p-4">
<div className="grid lg:grid-cols-3 gap-4">
<div>
<label className="text-sm text-slate-600">Plan Name</label>
<label className="text-sm text-slate-600">Plan Condition</label>
<input type="text" value={newSwapName} onChange={(e) => setNewSwapName(e.target.value)} placeholder="e.g., Standard Station" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
</div>
<div>
@@ -3168,7 +3168,7 @@ export default function CompanySettingsPage() {
<div className="p-4">
<div className="grid lg:grid-cols-3 gap-4">
<div>
<label className="text-sm text-slate-600">Plan Name</label>
<label className="text-sm text-slate-600">Plan Condition</label>
<input type="text" value={plan.name} onChange={(e) => { const updated = [...settings.plans.swapStation]; updated[idx].name = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, swapStation: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
</div>
<div>
@@ -3255,7 +3255,7 @@ export default function CompanySettingsPage() {
<div className="p-4">
<div className="grid lg:grid-cols-3 gap-4">
<div>
<label className="text-sm text-slate-600">Plan Name</label>
<label className="text-sm text-slate-600">Plan Condition</label>
<input type="text" value={newRiderName} onChange={(e) => setNewRiderName(e.target.value)} placeholder="e.g., Premium Rider Plan" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
</div>
<div>
@@ -3334,7 +3334,7 @@ export default function CompanySettingsPage() {
<div className="p-4">
<div className="grid lg:grid-cols-3 gap-4">
<div>
<label className="text-sm text-slate-600">Plan Name</label>
<label className="text-sm text-slate-600">Plan Condition</label>
<input type="text" value={plan.name} onChange={(e) => { const updated = [...settings.plans.riderRequest]; updated[idx].name = e.target.value; setSettings({ ...settings, plans: { ...settings.plans, riderRequest: updated } }); }} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mt-1" />
</div>
<div>