Edited layout of mmddyyyy and time dropdown. Changed algorithm for determining pricing so that it choosest the cheapest option for users

This commit is contained in:
jackiettran
2026-01-01 14:46:40 -05:00
parent 3d0e553620
commit fd2312fe47
3 changed files with 650 additions and 210 deletions

View File

@@ -0,0 +1,376 @@
const RentalDurationCalculator = require("../../../utils/rentalDurationCalculator");
describe("RentalDurationCalculator - Hybrid Pricing", () => {
// Helper to create ISO date strings
const date = (dateStr, time = "10:00") => {
return `2024-01-${dateStr.padStart(2, "0")}T${time}:00.000Z`;
};
describe("calculateRentalCost", () => {
describe("Basic single-tier pricing", () => {
test("should calculate hourly rate for short rentals", () => {
const item = { pricePerHour: 10 };
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("01", "13:00"),
item
);
expect(result).toBe(30); // 3 hours * $10
});
test("should calculate daily rate for exact day rentals", () => {
const item = { pricePerDay: 50 };
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("03", "10:00"),
item
);
expect(result).toBe(100); // 2 days * $50
});
test("should calculate weekly rate for exact week rentals", () => {
const item = { pricePerWeek: 200 };
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("15", "10:00"),
item
);
expect(result).toBe(400); // 2 weeks * $200
});
test("should calculate monthly rate for exact month rentals", () => {
const item = { pricePerMonth: 600 };
const result = RentalDurationCalculator.calculateRentalCost(
"2024-01-01T10:00:00.000Z",
"2024-03-01T10:00:00.000Z", // 60 days = exactly 2 months (using 30-day months)
item
);
expect(result).toBe(1200); // 2 months * $600
});
});
describe("Hybrid day + hours pricing", () => {
test("should combine day and hours for 26-hour rental", () => {
const item = { pricePerDay: 50, pricePerHour: 10 };
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "15:00"),
date("02", "17:00"), // 26 hours
item
);
expect(result).toBe(70); // 1 day ($50) + 2 hours ($20)
});
test("should combine day and hours for 27-hour rental", () => {
const item = { pricePerDay: 50, pricePerHour: 10 };
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("02", "13:00"), // 27 hours
item
);
expect(result).toBe(80); // 1 day ($50) + 3 hours ($30)
});
test("should choose cheaper option - round up when hourly is expensive", () => {
// 25 hours: daily = $40, hourly = $50
// Hybrid: 1 day ($40) + 1 hour ($50) = $90
// Round-up: 2 days ($80)
// Expected: $80 (round-up wins)
const item = { pricePerDay: 40, pricePerHour: 50 };
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("02", "11:00"), // 25 hours
item
);
expect(result).toBe(80); // 2 days (cheaper than 1d + 1h)
});
test("should choose cheaper option - hybrid when hourly is cheap", () => {
// 25 hours: daily = $50, hourly = $5
// Hybrid: 1 day ($50) + 1 hour ($5) = $55
// Round-up: 2 days ($100)
// Expected: $55 (hybrid wins)
const item = { pricePerDay: 50, pricePerHour: 5 };
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("02", "11:00"), // 25 hours
item
);
expect(result).toBe(55); // 1 day + 1 hour (cheaper)
});
});
describe("Hybrid week + days pricing", () => {
test("should combine week and days for 10-day rental", () => {
const item = { pricePerWeek: 200, pricePerDay: 50 };
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("11", "10:00"), // 10 days
item
);
expect(result).toBe(350); // 1 week ($200) + 3 days ($150)
});
test("should combine week and days for 9-day rental", () => {
const item = { pricePerWeek: 200, pricePerDay: 50 };
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("10", "10:00"), // 9 days
item
);
expect(result).toBe(300); // 1 week ($200) + 2 days ($100)
});
test("should round up to 2 weeks when daily cost exceeds week", () => {
// 10 days: weekly = $100, daily = $50
// Hybrid: 1 week ($100) + 3 days ($150) = $250
// Round-up: 2 weeks ($200)
// Expected: $200 (round-up wins)
const item = { pricePerWeek: 100, pricePerDay: 50 };
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("11", "10:00"), // 10 days
item
);
expect(result).toBe(200); // 2 weeks (cheaper)
});
});
describe("Hybrid month + weeks + days pricing", () => {
test("should combine month, week, and days for 38-day rental", () => {
const item = { pricePerMonth: 600, pricePerWeek: 200, pricePerDay: 50 };
const result = RentalDurationCalculator.calculateRentalCost(
"2024-01-01T10:00:00.000Z",
"2024-02-08T10:00:00.000Z", // 38 days
item
);
expect(result).toBe(850); // 1 month ($600) + 1 week ($200) + 1 day ($50)
});
test("should combine month and days for 35-day rental without weekly", () => {
const item = { pricePerMonth: 600, pricePerDay: 50 };
const result = RentalDurationCalculator.calculateRentalCost(
"2024-01-01T10:00:00.000Z",
"2024-02-05T10:00:00.000Z", // 35 days
item
);
expect(result).toBe(850); // 1 month ($600) + 5 days ($250)
});
});
describe("Full hybrid: month + week + day + hour", () => {
test("should combine all tiers for complex duration", () => {
const item = {
pricePerMonth: 600,
pricePerWeek: 200,
pricePerDay: 50,
pricePerHour: 10,
};
// 38 days + 5 hours
const result = RentalDurationCalculator.calculateRentalCost(
"2024-01-01T10:00:00.000Z",
"2024-02-08T15:00:00.000Z",
item
);
// 1 month + 1 week + 1 day + 5 hours = $600 + $200 + $50 + $50 = $900
expect(result).toBe(900);
});
});
describe("Missing tier fallback", () => {
test("should round up to days when no hourly rate", () => {
const item = { pricePerDay: 50 }; // No hourly
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "15:00"),
date("02", "17:00"), // 26 hours
item
);
expect(result).toBe(100); // 2 days (rounded up)
});
test("should round up to weeks when no daily rate", () => {
const item = { pricePerWeek: 200 }; // No daily
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("10", "10:00"), // 9 days
item
);
expect(result).toBe(400); // 2 weeks (rounded up)
});
test("should round up to months when no weekly or daily rate", () => {
const item = { pricePerMonth: 600 }; // Only monthly
const result = RentalDurationCalculator.calculateRentalCost(
"2024-01-01T10:00:00.000Z",
"2024-02-15T10:00:00.000Z", // 45 days
item
);
expect(result).toBe(1200); // 2 months (rounded up)
});
test("should use hourly only when no larger tiers", () => {
const item = { pricePerHour: 10 }; // Only hourly
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("03", "10:00"), // 48 hours
item
);
expect(result).toBe(480); // 48 hours * $10
});
});
describe("Edge cases", () => {
test("should return 0 for zero duration", () => {
const item = { pricePerDay: 50 };
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("01", "10:00"),
item
);
expect(result).toBe(0);
});
test("should return 0 for negative duration", () => {
const item = { pricePerDay: 50 };
const result = RentalDurationCalculator.calculateRentalCost(
date("02", "10:00"),
date("01", "10:00"), // End before start
item
);
expect(result).toBe(0);
});
test("should return 0 for free items (no pricing)", () => {
const item = {}; // No pricing
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("05", "10:00"),
item
);
expect(result).toBe(0);
});
test("should return 0 when all prices are 0", () => {
const item = {
pricePerHour: 0,
pricePerDay: 0,
pricePerWeek: 0,
pricePerMonth: 0,
};
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("05", "10:00"),
item
);
expect(result).toBe(0);
});
test("should handle decimal prices", () => {
const item = { pricePerHour: 5.5 };
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("01", "13:00"), // 3 hours
item
);
expect(result).toBe(16.5); // 3 * $5.50
});
test("should round result to 2 decimal places", () => {
const item = { pricePerHour: 3.33 };
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "10:00"),
date("01", "13:00"), // 3 hours
item
);
expect(result).toBe(9.99); // 3 * $3.33
});
test("should handle string prices in item", () => {
const item = { pricePerDay: "50", pricePerHour: "10" };
const result = RentalDurationCalculator.calculateRentalCost(
date("01", "15:00"),
date("02", "17:00"), // 26 hours
item
);
expect(result).toBe(70); // 1 day + 2 hours
});
test("should handle very short rentals (less than 1 hour)", () => {
const item = { pricePerHour: 10 };
const result = RentalDurationCalculator.calculateRentalCost(
"2024-01-01T10:00:00.000Z",
"2024-01-01T10:30:00.000Z", // 30 minutes
item
);
expect(result).toBe(10); // Rounds up to 1 hour
});
});
describe("Real-world scenarios", () => {
test("Scenario: Tool rental 3 days 4 hours", () => {
const item = { pricePerDay: 25, pricePerHour: 5 };
const result = RentalDurationCalculator.calculateRentalCost(
"2024-01-01T08:00:00.000Z",
"2024-01-04T12:00:00.000Z", // 3 days + 4 hours
item
);
expect(result).toBe(95); // 3 days ($75) + 4 hours ($20)
});
test("Scenario: Equipment rental 2 weeks 3 days", () => {
const item = { pricePerWeek: 100, pricePerDay: 20 };
const result = RentalDurationCalculator.calculateRentalCost(
"2024-01-01T10:00:00.000Z",
"2024-01-18T10:00:00.000Z", // 17 days = 2 weeks + 3 days
item
);
expect(result).toBe(260); // 2 weeks ($200) + 3 days ($60)
});
test("Scenario: Long-term rental 2 months 1 week", () => {
const item = { pricePerMonth: 500, pricePerWeek: 150 };
const result = RentalDurationCalculator.calculateRentalCost(
"2024-01-01T10:00:00.000Z",
"2024-03-08T10:00:00.000Z", // ~67 days = 2 months + 1 week
item
);
expect(result).toBe(1150); // 2 months ($1000) + 1 week ($150)
});
});
});
describe("buildPricingTiers", () => {
test("should return empty array for items with no pricing", () => {
const item = {};
const tiers = RentalDurationCalculator.buildPricingTiers(item);
expect(tiers).toHaveLength(0);
});
test("should build tiers from largest to smallest", () => {
const item = {
pricePerMonth: 600,
pricePerWeek: 200,
pricePerDay: 50,
pricePerHour: 10,
};
const tiers = RentalDurationCalculator.buildPricingTiers(item);
expect(tiers).toHaveLength(4);
expect(tiers[0].name).toBe("month");
expect(tiers[1].name).toBe("week");
expect(tiers[2].name).toBe("day");
expect(tiers[3].name).toBe("hour");
});
test("should skip tiers with 0 or null pricing", () => {
const item = {
pricePerMonth: 0,
pricePerWeek: null,
pricePerDay: 50,
pricePerHour: 10,
};
const tiers = RentalDurationCalculator.buildPricingTiers(item);
expect(tiers).toHaveLength(2);
expect(tiers[0].name).toBe("day");
expect(tiers[1].name).toBe("hour");
});
});
});

View File

@@ -1,61 +1,143 @@
/**
* Rental Duration Calculator with Hybrid Pricing
*
* Finds the optimal (cheapest) combination of pricing tiers for any rental duration.
* Supports combining hours + days + weeks + months for best pricing.
*
* Example: 26 hours with $10/hr and $50/day
* - Old logic: 2 days × $50 = $100
* - New logic: 1 day × $50 + 2 hours × $10 = $70
*/
class RentalDurationCalculator {
// Time constants in milliseconds
static HOUR_MS = 60 * 60 * 1000;
static DAY_MS = 24 * 60 * 60 * 1000;
static WEEK_MS = 7 * 24 * 60 * 60 * 1000;
static MONTH_MS = 30 * 24 * 60 * 60 * 1000;
/**
* Build available pricing tiers from item, sorted largest to smallest
* @param {Object} item - Item object with pricing information
* @returns {Array} Array of {name, price, durationMs} sorted largest to smallest
*/
static buildPricingTiers(item) {
const tiers = [];
if (item.pricePerMonth && Number(item.pricePerMonth) > 0) {
tiers.push({
name: "month",
price: Number(item.pricePerMonth),
durationMs: this.MONTH_MS,
});
}
if (item.pricePerWeek && Number(item.pricePerWeek) > 0) {
tiers.push({
name: "week",
price: Number(item.pricePerWeek),
durationMs: this.WEEK_MS,
});
}
if (item.pricePerDay && Number(item.pricePerDay) > 0) {
tiers.push({
name: "day",
price: Number(item.pricePerDay),
durationMs: this.DAY_MS,
});
}
if (item.pricePerHour && Number(item.pricePerHour) > 0) {
tiers.push({
name: "hour",
price: Number(item.pricePerHour),
durationMs: this.HOUR_MS,
});
}
return tiers;
}
/**
* Recursively calculate optimal price for remaining duration
* Uses greedy approach: try largest tier first, then handle remainder with smaller tiers
* Compares hybrid price vs round-up price and returns the cheaper option
*
* @param {number} remainingMs - Remaining duration in milliseconds
* @param {Array} tiers - Available pricing tiers (largest to smallest)
* @param {number} tierIndex - Current tier being considered
* @returns {number} Optimal price for this duration
*/
static calculateOptimalForDuration(remainingMs, tiers, tierIndex = 0) {
// Base case: no remaining time
if (remainingMs <= 0) {
return 0;
}
// Base case: no more tiers available - round up with smallest available tier
if (tierIndex >= tiers.length) {
const smallestTier = tiers[tiers.length - 1];
const unitsNeeded = Math.ceil(remainingMs / smallestTier.durationMs);
return unitsNeeded * smallestTier.price;
}
const currentTier = tiers[tierIndex];
const completeUnits = Math.floor(remainingMs / currentTier.durationMs);
const remainder = remainingMs - completeUnits * currentTier.durationMs;
if (completeUnits > 0) {
// Option 1: Use complete units of current tier + handle remainder with smaller tiers
const currentCost = completeUnits * currentTier.price;
const remainderCost = this.calculateOptimalForDuration(
remainder,
tiers,
tierIndex + 1
);
const hybridCost = currentCost + remainderCost;
// Option 2: Round up to one more unit of current tier (may be cheaper if remainder cost is high)
const roundUpCost = (completeUnits + 1) * currentTier.price;
// Return the cheaper option when there's a remainder to handle
return remainder > 0 ? Math.min(hybridCost, roundUpCost) : currentCost;
} else {
// Duration too small for this tier, try next smaller tier
return this.calculateOptimalForDuration(remainingMs, tiers, tierIndex + 1);
}
}
/**
* Calculate rental cost based on duration and item pricing tiers
* Uses hybrid pricing to find the optimal (cheapest) combination of tiers
*
* @param {Date|string} startDateTime - Rental start date/time
* @param {Date|string} endDateTime - Rental end date/time
* @param {Object} item - Item object with pricing information
* @returns {number} Total rental cost
*/
static calculateRentalCost(startDateTime, endDateTime, item) {
const rentalStartDateTime = new Date(startDateTime);
const rentalEndDateTime = new Date(endDateTime);
const start = new Date(startDateTime);
const end = new Date(endDateTime);
// Calculate rental duration
const diffMs = rentalEndDateTime.getTime() - rentalStartDateTime.getTime();
const diffHours = Math.ceil(diffMs / (1000 * 60 * 60));
const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
const diffWeeks = Math.ceil(diffDays / 7);
// Calculate total duration in milliseconds
const durationMs = end.getTime() - start.getTime();
// Calculate difference in calendar months
let diffMonths = (rentalEndDateTime.getFullYear() - rentalStartDateTime.getFullYear()) * 12;
diffMonths += rentalEndDateTime.getMonth() - rentalStartDateTime.getMonth();
// Add 1 if we're past the start day/time in the end month
if (rentalEndDateTime.getDate() > rentalStartDateTime.getDate()) {
diffMonths += 1;
} else if (rentalEndDateTime.getDate() === rentalStartDateTime.getDate() &&
rentalEndDateTime.getTime() > rentalStartDateTime.getTime()) {
diffMonths += 1;
if (durationMs <= 0) {
return 0;
}
diffMonths = Math.max(1, diffMonths);
// Build available pricing tiers
const tiers = this.buildPricingTiers(item);
// Calculate base amount based on duration (tiered pricing)
let totalAmount;
if (item.pricePerHour && diffHours < 24) {
// Use hourly rate for rentals under 24 hours
totalAmount = diffHours * Number(item.pricePerHour);
} else if (diffDays <= 7 && item.pricePerDay) {
// Use daily rate for rentals <= 7 days
totalAmount = diffDays * Number(item.pricePerDay);
} else if (diffMonths <= 1 && item.pricePerWeek) {
// Use weekly rate for rentals <= 1 calendar month
totalAmount = diffWeeks * Number(item.pricePerWeek);
} else if (diffMonths > 1 && item.pricePerMonth) {
// Use monthly rate for rentals > 1 calendar month
totalAmount = diffMonths * Number(item.pricePerMonth);
} else if (item.pricePerWeek) {
// Fallback to weekly rate if monthly not available
totalAmount = diffWeeks * Number(item.pricePerWeek);
} else if (item.pricePerDay) {
// Fallback to daily rate
totalAmount = diffDays * Number(item.pricePerDay);
} else {
totalAmount = 0;
// No pricing tiers = free item
if (tiers.length === 0) {
return 0;
}
return totalAmount;
// Calculate optimal price using hybrid tier combination
const optimalPrice = this.calculateOptimalForDuration(durationMs, tiers);
return parseFloat(optimalPrice.toFixed(2));
}
}

View File

@@ -676,98 +676,92 @@ const ItemDetail: React.FC = () => {
<label className="form-label fw-medium mb-2">
Start
</label>
<div className="d-flex gap-2">
<div style={{ flex: "1 1 50%" }}>
<DatePicker
selected={rentalDates.startDate}
onChange={(date: Date | null) =>
setRentalDates((prev) => ({
...prev,
startDate: date,
}))
}
minDate={new Date()}
dateFormat="MM/dd/yyyy"
placeholderText="mm/dd/yyyy"
className="form-control form-control-lg w-100"
popperProps={{ strategy: "fixed" }}
/>
</div>
<div style={{ flex: "1 1 50%" }}>
<select
className="form-select form-select-lg"
value={rentalDates.startTime}
onChange={(e) =>
setRentalDates((prev) => ({
...prev,
startTime: e.target.value,
}))
}
disabled={!rentalDates.startDate}
>
<option value="">Pickup</option>
{generateTimeOptions(
rentalDates.startDate
).map((option) => (
<option
key={option.value}
value={option.value}
>
{option.label}
</option>
))}
</select>
</div>
<div style={{ width: "60%" }}>
<DatePicker
selected={rentalDates.startDate}
onChange={(date: Date | null) =>
setRentalDates((prev) => ({
...prev,
startDate: date,
}))
}
minDate={new Date()}
dateFormat="MM/dd/yyyy"
placeholderText="mm/dd/yyyy"
className="form-control form-control-lg mb-2 w-100"
popperProps={{ strategy: "fixed" }}
/>
</div>
<select
className="form-select form-select-lg"
style={{ width: "60%" }}
value={rentalDates.startTime}
onChange={(e) =>
setRentalDates((prev) => ({
...prev,
startTime: e.target.value,
}))
}
disabled={!rentalDates.startDate}
>
<option value="">Pickup Time</option>
{generateTimeOptions(rentalDates.startDate).map(
(option) => (
<option
key={option.value}
value={option.value}
>
{option.label}
</option>
)
)}
</select>
</div>
<div className="mb-3">
<label className="form-label fw-medium mb-2">
End
</label>
<div className="d-flex gap-2">
<div style={{ flex: "1 1 50%" }}>
<DatePicker
selected={rentalDates.endDate}
onChange={(date: Date | null) =>
setRentalDates((prev) => ({
...prev,
endDate: date,
}))
}
minDate={rentalDates.startDate || new Date()}
dateFormat="MM/dd/yyyy"
placeholderText="mm/dd/yyyy"
className="form-control form-control-lg w-100"
popperProps={{ strategy: "fixed" }}
/>
</div>
<div style={{ flex: "1 1 50%" }}>
<select
className="form-select form-select-lg"
value={rentalDates.endTime}
onChange={(e) =>
setRentalDates((prev) => ({
...prev,
endTime: e.target.value,
}))
}
disabled={!rentalDates.endDate}
>
<option value="">Return</option>
{generateTimeOptions(rentalDates.endDate).map(
(option) => (
<option
key={option.value}
value={option.value}
>
{option.label}
</option>
)
)}
</select>
</div>
<div style={{ width: "60%" }}>
<DatePicker
selected={rentalDates.endDate}
onChange={(date: Date | null) =>
setRentalDates((prev) => ({
...prev,
endDate: date,
}))
}
minDate={rentalDates.startDate || new Date()}
dateFormat="MM/dd/yyyy"
placeholderText="mm/dd/yyyy"
className="form-control form-control-lg mb-2 w-100"
popperProps={{ strategy: "fixed" }}
/>
</div>
<select
className="form-select form-select-lg"
style={{ width: "60%" }}
value={rentalDates.endTime}
onChange={(e) =>
setRentalDates((prev) => ({
...prev,
endTime: e.target.value,
}))
}
disabled={!rentalDates.endDate}
>
<option value="">Return Time</option>
{generateTimeOptions(rentalDates.endDate).map(
(option) => (
<option
key={option.value}
value={option.value}
>
{option.label}
</option>
)
)}
</select>
</div>
{rentalDates.startDate &&
@@ -802,7 +796,7 @@ const ItemDetail: React.FC = () => {
{!isOwner && item.isAvailable && !isAlreadyRenting && (
<div className="d-grid">
<button
className="btn btn-primary"
className="btn btn-primary btn-lg"
onClick={handleRent}
disabled={
!rentalDates.startDate ||
@@ -819,7 +813,7 @@ const ItemDetail: React.FC = () => {
{!isOwner && isAlreadyRenting && (
<div className="d-grid">
<button
className="btn btn-success"
className="btn btn-success btn-lg"
disabled
style={{ opacity: 0.8 }}
>
@@ -928,96 +922,84 @@ const ItemDetail: React.FC = () => {
<label className="form-label fw-medium mb-2">
Start
</label>
<div className="d-flex gap-2">
<div style={{ flex: "1 1 50%" }}>
<DatePicker
selected={rentalDates.startDate}
onChange={(date: Date | null) =>
setRentalDates((prev) => ({
...prev,
startDate: date,
}))
}
minDate={new Date()}
dateFormat="MM/dd/yyyy"
placeholderText="mm/dd/yyyy"
className="form-control w-100"
/>
</div>
<div style={{ flex: "1 1 50%" }}>
<select
className="form-select"
value={rentalDates.startTime}
onChange={(e) =>
setRentalDates((prev) => ({
...prev,
startTime: e.target.value,
}))
}
disabled={!rentalDates.startDate}
>
<option value="">Pickup</option>
{generateTimeOptions(rentalDates.startDate).map(
(option) => (
<option
key={option.value}
value={option.value}
>
{option.label}
</option>
)
)}
</select>
</div>
<div style={{ width: "60%" }}>
<DatePicker
selected={rentalDates.startDate}
onChange={(date: Date | null) =>
setRentalDates((prev) => ({
...prev,
startDate: date,
}))
}
minDate={new Date()}
dateFormat="MM/dd/yyyy"
placeholderText="mm/dd/yyyy"
className="form-control mb-2 w-100"
/>
</div>
<select
className="form-select"
style={{ width: "60%" }}
value={rentalDates.startTime}
onChange={(e) =>
setRentalDates((prev) => ({
...prev,
startTime: e.target.value,
}))
}
disabled={!rentalDates.startDate}
>
<option value="">Pickup Time</option>
{generateTimeOptions(rentalDates.startDate).map(
(option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
)
)}
</select>
</div>
<div className="mb-3">
<label className="form-label fw-medium mb-2">
End
</label>
<div className="d-flex gap-2">
<div style={{ flex: "1 1 50%" }}>
<DatePicker
selected={rentalDates.endDate}
onChange={(date: Date | null) =>
setRentalDates((prev) => ({
...prev,
endDate: date,
}))
}
minDate={rentalDates.startDate || new Date()}
dateFormat="MM/dd/yyyy"
placeholderText="mm/dd/yyyy"
className="form-control w-100"
/>
</div>
<div style={{ flex: "1 1 50%" }}>
<select
className="form-select"
value={rentalDates.endTime}
onChange={(e) =>
setRentalDates((prev) => ({
...prev,
endTime: e.target.value,
}))
}
disabled={!rentalDates.endDate}
>
<option value="">Return</option>
{generateTimeOptions(rentalDates.endDate).map(
(option) => (
<option
key={option.value}
value={option.value}
>
{option.label}
</option>
)
)}
</select>
</div>
<div style={{ width: "60%" }}>
<DatePicker
selected={rentalDates.endDate}
onChange={(date: Date | null) =>
setRentalDates((prev) => ({
...prev,
endDate: date,
}))
}
minDate={rentalDates.startDate || new Date()}
dateFormat="MM/dd/yyyy"
placeholderText="mm/dd/yyyy"
className="form-control mb-2 w-100"
/>
</div>
<select
className="form-select"
style={{ width: "60%" }}
value={rentalDates.endTime}
onChange={(e) =>
setRentalDates((prev) => ({
...prev,
endTime: e.target.value,
}))
}
disabled={!rentalDates.endDate}
>
<option value="">Return Time</option>
{generateTimeOptions(rentalDates.endDate).map(
(option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
)
)}
</select>
</div>
{rentalDates.startDate &&