removed cron job that made rentals active. Now whether or not the rental is active is determined on the fly
This commit is contained in:
@@ -1,101 +0,0 @@
|
|||||||
const cron = require("node-cron");
|
|
||||||
const { Rental } = require("../models");
|
|
||||||
const { Op } = require("sequelize");
|
|
||||||
const logger = require("../utils/logger");
|
|
||||||
|
|
||||||
const statusUpdateSchedule = "*/15 * * * *"; // Run every 15 minutes
|
|
||||||
|
|
||||||
class RentalStatusJob {
|
|
||||||
static startScheduledStatusUpdates() {
|
|
||||||
console.log("Starting automated rental status updates...");
|
|
||||||
|
|
||||||
const statusJob = cron.schedule(
|
|
||||||
statusUpdateSchedule,
|
|
||||||
async () => {
|
|
||||||
try {
|
|
||||||
await this.activateStartedRentals();
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error in scheduled rental status update", {
|
|
||||||
error: error.message,
|
|
||||||
stack: error.stack
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scheduled: false,
|
|
||||||
timezone: "America/New_York",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Start the job
|
|
||||||
statusJob.start();
|
|
||||||
|
|
||||||
console.log("Rental status job scheduled:");
|
|
||||||
console.log("- Status updates every 15 minutes: " + statusUpdateSchedule);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusJob,
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
statusJob.stop();
|
|
||||||
console.log("Rental status job stopped");
|
|
||||||
},
|
|
||||||
|
|
||||||
getStatus() {
|
|
||||||
return {
|
|
||||||
statusJobRunning: statusJob.getStatus() === "scheduled",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static async activateStartedRentals() {
|
|
||||||
try {
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
// Find all confirmed rentals where start time has arrived
|
|
||||||
const rentalsToActivate = await Rental.findAll({
|
|
||||||
where: {
|
|
||||||
status: "confirmed",
|
|
||||||
startDateTime: {
|
|
||||||
[Op.lte]: now,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (rentalsToActivate.length === 0) {
|
|
||||||
return { activated: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update all matching rentals to active status
|
|
||||||
const rentalIds = rentalsToActivate.map((r) => r.id);
|
|
||||||
const [updateCount] = await Rental.update(
|
|
||||||
{ status: "active" },
|
|
||||||
{
|
|
||||||
where: {
|
|
||||||
id: {
|
|
||||||
[Op.in]: rentalIds,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.info("Activated started rentals", {
|
|
||||||
count: updateCount,
|
|
||||||
rentalIds: rentalIds,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Activated ${updateCount} rentals that have started`);
|
|
||||||
|
|
||||||
return { activated: updateCount, rentalIds };
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error activating started rentals", {
|
|
||||||
error: error.message,
|
|
||||||
stack: error.stack,
|
|
||||||
});
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = RentalStatusJob;
|
|
||||||
@@ -14,8 +14,24 @@ const emailServices = require("../services/email");
|
|||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
const { validateS3Keys } = require("../utils/s3KeyValidator");
|
const { validateS3Keys } = require("../utils/s3KeyValidator");
|
||||||
const { IMAGE_LIMITS } = require("../config/imageLimits");
|
const { IMAGE_LIMITS } = require("../config/imageLimits");
|
||||||
|
const { isActive, getEffectiveStatus } = require("../utils/rentalStatus");
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
// Helper to add displayStatus to rental response
|
||||||
|
const addDisplayStatus = (rental) => {
|
||||||
|
if (!rental) return rental;
|
||||||
|
const rentalJson = rental.toJSON ? rental.toJSON() : rental;
|
||||||
|
return {
|
||||||
|
...rentalJson,
|
||||||
|
displayStatus: getEffectiveStatus(rentalJson),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to add displayStatus to array of rentals
|
||||||
|
const addDisplayStatusToArray = (rentals) => {
|
||||||
|
return rentals.map(addDisplayStatus);
|
||||||
|
};
|
||||||
|
|
||||||
// Helper function to check and update review visibility
|
// Helper function to check and update review visibility
|
||||||
const checkAndUpdateReviewVisibility = async (rental) => {
|
const checkAndUpdateReviewVisibility = async (rental) => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -75,7 +91,7 @@ router.get("/renting", authenticateToken, async (req, res) => {
|
|||||||
order: [["createdAt", "DESC"]],
|
order: [["createdAt", "DESC"]],
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json(rentals);
|
res.json(addDisplayStatusToArray(rentals));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const reqLogger = logger.withRequestId(req.id);
|
const reqLogger = logger.withRequestId(req.id);
|
||||||
reqLogger.error("Error in renting route", {
|
reqLogger.error("Error in renting route", {
|
||||||
@@ -103,7 +119,7 @@ router.get("/owning", authenticateToken, async (req, res) => {
|
|||||||
order: [["createdAt", "DESC"]],
|
order: [["createdAt", "DESC"]],
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json(rentals);
|
res.json(addDisplayStatusToArray(rentals));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const reqLogger = logger.withRequestId(req.id);
|
const reqLogger = logger.withRequestId(req.id);
|
||||||
reqLogger.error("Error in owning route", {
|
reqLogger.error("Error in owning route", {
|
||||||
@@ -167,7 +183,7 @@ router.get("/:id", authenticateToken, async (req, res) => {
|
|||||||
.json({ error: "Unauthorized to view this rental" });
|
.json({ error: "Unauthorized to view this rental" });
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json(rental);
|
res.json(addDisplayStatus(rental));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const reqLogger = logger.withRequestId(req.id);
|
const reqLogger = logger.withRequestId(req.id);
|
||||||
reqLogger.error("Error fetching rental", {
|
reqLogger.error("Error fetching rental", {
|
||||||
@@ -235,10 +251,11 @@ router.post("/", authenticateToken, requireVerifiedEmail, async (req, res) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Check for overlapping rentals using datetime ranges
|
// Check for overlapping rentals using datetime ranges
|
||||||
|
// Note: "active" rentals are stored as "confirmed" with startDateTime in the past
|
||||||
const overlappingRental = await Rental.findOne({
|
const overlappingRental = await Rental.findOne({
|
||||||
where: {
|
where: {
|
||||||
itemId,
|
itemId,
|
||||||
status: { [Op.in]: ["confirmed", "active"] },
|
status: "confirmed",
|
||||||
[Op.or]: [
|
[Op.or]: [
|
||||||
{
|
{
|
||||||
[Op.and]: [
|
[Op.and]: [
|
||||||
@@ -875,7 +892,7 @@ router.post("/:id/mark-completed", authenticateToken, async (req, res) => {
|
|||||||
.json({ error: "Only owners can mark rentals as completed" });
|
.json({ error: "Only owners can mark rentals as completed" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rental.status !== "active") {
|
if (!isActive(rental)) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: "Can only mark active rentals as completed",
|
error: "Can only mark active rentals as completed",
|
||||||
});
|
});
|
||||||
@@ -1197,7 +1214,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res, next) => {
|
|||||||
.json({ error: "Only the item owner can mark return status" });
|
.json({ error: "Only the item owner can mark return status" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rental.status !== "active") {
|
if (!isActive(rental)) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: "Can only mark return status for active rentals",
|
error: "Can only mark return status for active rentals",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ const uploadRoutes = require("./routes/upload");
|
|||||||
const healthRoutes = require("./routes/health");
|
const healthRoutes = require("./routes/health");
|
||||||
|
|
||||||
const PayoutProcessor = require("./jobs/payoutProcessor");
|
const PayoutProcessor = require("./jobs/payoutProcessor");
|
||||||
const RentalStatusJob = require("./jobs/rentalStatusJob");
|
|
||||||
const ConditionCheckReminderJob = require("./jobs/conditionCheckReminder");
|
const ConditionCheckReminderJob = require("./jobs/conditionCheckReminder");
|
||||||
const emailServices = require("./services/email");
|
const emailServices = require("./services/email");
|
||||||
const s3Service = require("./services/s3Service");
|
const s3Service = require("./services/s3Service");
|
||||||
@@ -230,10 +229,6 @@ sequelize
|
|||||||
const payoutJobs = PayoutProcessor.startScheduledPayouts();
|
const payoutJobs = PayoutProcessor.startScheduledPayouts();
|
||||||
logger.info("Payout processor started");
|
logger.info("Payout processor started");
|
||||||
|
|
||||||
// Start the rental status job
|
|
||||||
const rentalStatusJobs = RentalStatusJob.startScheduledStatusUpdates();
|
|
||||||
logger.info("Rental status job started");
|
|
||||||
|
|
||||||
// Start the condition check reminder job
|
// Start the condition check reminder job
|
||||||
const conditionCheckJobs =
|
const conditionCheckJobs =
|
||||||
ConditionCheckReminderJob.startScheduledReminders();
|
ConditionCheckReminderJob.startScheduledReminders();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const { ConditionCheck, Rental, User } = require("../models");
|
const { ConditionCheck, Rental, User } = require("../models");
|
||||||
const { Op } = require("sequelize");
|
const { Op } = require("sequelize");
|
||||||
|
const { isActive } = require("../utils/rentalStatus");
|
||||||
|
|
||||||
class ConditionCheckService {
|
class ConditionCheckService {
|
||||||
/**
|
/**
|
||||||
@@ -70,7 +71,7 @@ class ConditionCheckService {
|
|||||||
canSubmit =
|
canSubmit =
|
||||||
now >= timeWindow.start &&
|
now >= timeWindow.start &&
|
||||||
now <= timeWindow.end &&
|
now <= timeWindow.end &&
|
||||||
rental.status === "active";
|
isActive(rental);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "rental_end_renter":
|
case "rental_end_renter":
|
||||||
@@ -80,7 +81,7 @@ class ConditionCheckService {
|
|||||||
canSubmit =
|
canSubmit =
|
||||||
now >= timeWindow.start &&
|
now >= timeWindow.start &&
|
||||||
now <= timeWindow.end &&
|
now <= timeWindow.end &&
|
||||||
rental.status === "active";
|
isActive(rental);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "post_rental_owner":
|
case "post_rental_owner":
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const { Rental, Item, ConditionCheck, User } = require("../models");
|
const { Rental, Item, ConditionCheck, User } = require("../models");
|
||||||
const LateReturnService = require("./lateReturnService");
|
const LateReturnService = require("./lateReturnService");
|
||||||
const emailServices = require("./email");
|
const emailServices = require("./email");
|
||||||
|
const { isActive } = require("../utils/rentalStatus");
|
||||||
|
|
||||||
class DamageAssessmentService {
|
class DamageAssessmentService {
|
||||||
/**
|
/**
|
||||||
@@ -34,7 +35,7 @@ class DamageAssessmentService {
|
|||||||
throw new Error("Only the item owner can report damage");
|
throw new Error("Only the item owner can report damage");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rental.status !== "active") {
|
if (!isActive(rental)) {
|
||||||
throw new Error("Can only assess damage for active rentals");
|
throw new Error("Can only assess damage for active rentals");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const { Rental, Item, User } = require("../models");
|
const { Rental, Item, User } = require("../models");
|
||||||
const emailServices = require("./email");
|
const emailServices = require("./email");
|
||||||
|
const { isActive } = require("../utils/rentalStatus");
|
||||||
|
|
||||||
class LateReturnService {
|
class LateReturnService {
|
||||||
/**
|
/**
|
||||||
@@ -71,7 +72,7 @@ class LateReturnService {
|
|||||||
throw new Error("Rental not found");
|
throw new Error("Rental not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rental.status !== "active") {
|
if (!isActive(rental)) {
|
||||||
throw new Error("Can only process late returns for active rentals");
|
throw new Error("Can only process late returns for active rentals");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const { Rental } = require("../models");
|
const { Rental } = require("../models");
|
||||||
const StripeService = require("./stripeService");
|
const StripeService = require("./stripeService");
|
||||||
|
const { isActive } = require("../utils/rentalStatus");
|
||||||
|
|
||||||
class RefundService {
|
class RefundService {
|
||||||
/**
|
/**
|
||||||
@@ -69,8 +70,8 @@ class RefundService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if rental is active
|
// Check if rental is active (computed from confirmed + start time passed)
|
||||||
if (rental.status === "active") {
|
if (isActive(rental)) {
|
||||||
return {
|
return {
|
||||||
canCancel: false,
|
canCancel: false,
|
||||||
reason: "Cannot cancel active rental",
|
reason: "Cannot cancel active rental",
|
||||||
|
|||||||
@@ -790,11 +790,15 @@ describe('Rentals Routes', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /:id/mark-completed', () => {
|
describe('POST /:id/mark-completed', () => {
|
||||||
|
// Active status is computed: confirmed + startDateTime in the past
|
||||||
|
const pastDate = new Date();
|
||||||
|
pastDate.setHours(pastDate.getHours() - 1); // 1 hour ago
|
||||||
const mockRental = {
|
const mockRental = {
|
||||||
id: 1,
|
id: 1,
|
||||||
ownerId: 1,
|
ownerId: 1,
|
||||||
renterId: 2,
|
renterId: 2,
|
||||||
status: 'active',
|
status: 'confirmed',
|
||||||
|
startDateTime: pastDate,
|
||||||
update: jest.fn(),
|
update: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -837,7 +841,7 @@ describe('Rentals Routes', () => {
|
|||||||
|
|
||||||
expect(response.status).toBe(400);
|
expect(response.status).toBe(400);
|
||||||
expect(response.body).toEqual({
|
expect(response.body).toEqual({
|
||||||
error: 'Can only mark active or confirmed rentals as completed',
|
error: 'Can only mark active rentals as completed',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ describe('ConditionCheckService', () => {
|
|||||||
|
|
||||||
describe('submitConditionCheck', () => {
|
describe('submitConditionCheck', () => {
|
||||||
// Set rental dates relative to current time for valid time window
|
// Set rental dates relative to current time for valid time window
|
||||||
|
// Active status is computed: confirmed + startDateTime in the past
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const mockRental = {
|
const mockRental = {
|
||||||
id: 'rental-123',
|
id: 'rental-123',
|
||||||
@@ -17,7 +18,7 @@ describe('ConditionCheckService', () => {
|
|||||||
renterId: 'renter-789',
|
renterId: 'renter-789',
|
||||||
startDateTime: new Date(now.getTime() - 1000 * 60 * 60), // 1 hour ago
|
startDateTime: new Date(now.getTime() - 1000 * 60 * 60), // 1 hour ago
|
||||||
endDateTime: new Date(now.getTime() + 1000 * 60 * 60 * 24), // 24 hours from now
|
endDateTime: new Date(now.getTime() + 1000 * 60 * 60 * 24), // 24 hours from now
|
||||||
status: 'active'
|
status: 'confirmed' // Will be computed as "active" since startDateTime is in the past
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockPhotos = ['/uploads/photo1.jpg', '/uploads/photo2.jpg'];
|
const mockPhotos = ['/uploads/photo1.jpg', '/uploads/photo2.jpg'];
|
||||||
|
|||||||
@@ -27,11 +27,15 @@ describe('DamageAssessmentService', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Reset mockRental for each test to avoid state pollution
|
// Reset mockRental for each test to avoid state pollution
|
||||||
|
// Active status is computed: confirmed + startDateTime in the past
|
||||||
|
const pastDate = new Date();
|
||||||
|
pastDate.setHours(pastDate.getHours() - 1); // 1 hour ago
|
||||||
mockRental = {
|
mockRental = {
|
||||||
id: 'rental-123',
|
id: 'rental-123',
|
||||||
ownerId: 'owner-789',
|
ownerId: 'owner-789',
|
||||||
renterId: 'renter-456',
|
renterId: 'renter-456',
|
||||||
status: 'active',
|
status: 'confirmed',
|
||||||
|
startDateTime: pastDate,
|
||||||
item: { name: 'Test Camera', dailyRate: 100 },
|
item: { name: 'Test Camera', dailyRate: 100 },
|
||||||
update: jest.fn().mockResolvedValue({
|
update: jest.fn().mockResolvedValue({
|
||||||
id: 'rental-123',
|
id: 'rental-123',
|
||||||
|
|||||||
@@ -91,9 +91,11 @@ describe('LateReturnService', () => {
|
|||||||
let mockRental;
|
let mockRental;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
// Active status is computed: confirmed + startDateTime in the past
|
||||||
mockRental = {
|
mockRental = {
|
||||||
id: '123',
|
id: '123',
|
||||||
status: 'active',
|
status: 'confirmed',
|
||||||
|
startDateTime: new Date('2023-05-01T08:00:00Z'), // In the past
|
||||||
endDateTime: new Date('2023-06-01T10:00:00Z'),
|
endDateTime: new Date('2023-06-01T10:00:00Z'),
|
||||||
item: { pricePerHour: 10, name: 'Test Item' },
|
item: { pricePerHour: 10, name: 'Test Item' },
|
||||||
renterId: 'renter-123',
|
renterId: 'renter-123',
|
||||||
|
|||||||
@@ -229,8 +229,11 @@ describe('RefundService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject cancellation for active rental', () => {
|
it('should reject cancellation for active rental (computed from confirmed + past start)', () => {
|
||||||
const rental = { ...baseRental, status: 'active' };
|
// Active status is now computed: confirmed + startDateTime in the past
|
||||||
|
const pastDate = new Date();
|
||||||
|
pastDate.setHours(pastDate.getHours() - 1); // 1 hour ago
|
||||||
|
const rental = { ...baseRental, status: 'confirmed', startDateTime: pastDate };
|
||||||
const result = RefundService.validateCancellationEligibility(rental, 100);
|
const result = RefundService.validateCancellationEligibility(rental, 100);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
|
|||||||
41
backend/utils/rentalStatus.js
Normal file
41
backend/utils/rentalStatus.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Utility functions for computing rental status on-the-fly.
|
||||||
|
* The "active" status is computed based on timestamps rather than stored in the database.
|
||||||
|
* A rental is considered "active" when it has status "confirmed" and startDateTime has passed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the effective/display status of a rental.
|
||||||
|
* If the rental is "confirmed" and the start time has passed, returns "active".
|
||||||
|
* Otherwise returns the stored status.
|
||||||
|
* @param {Object} rental - The rental object with status and startDateTime
|
||||||
|
* @returns {string} The effective status
|
||||||
|
*/
|
||||||
|
function getEffectiveStatus(rental) {
|
||||||
|
const now = new Date();
|
||||||
|
if (
|
||||||
|
rental.status === "confirmed" &&
|
||||||
|
new Date(rental.startDateTime) <= now
|
||||||
|
) {
|
||||||
|
return "active";
|
||||||
|
}
|
||||||
|
return rental.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a rental is currently active.
|
||||||
|
* A rental is active when it's confirmed and the start time has passed.
|
||||||
|
* @param {Object} rental - The rental object with status and startDateTime
|
||||||
|
* @returns {boolean} True if the rental is currently active
|
||||||
|
*/
|
||||||
|
function isActive(rental) {
|
||||||
|
const now = new Date();
|
||||||
|
return (
|
||||||
|
rental.status === "confirmed" && new Date(rental.startDateTime) <= now
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getEffectiveStatus,
|
||||||
|
isActive,
|
||||||
|
};
|
||||||
@@ -41,11 +41,13 @@ const RentalCancellationModal: React.FC<RentalCancellationModalProps> = ({
|
|||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// Check if rental status allows cancellation before making API call
|
// Check if rental status allows cancellation before making API call
|
||||||
if (rental.status !== "pending" && rental.status !== "confirmed") {
|
// Use displayStatus as it includes computed "active" status
|
||||||
|
const effectiveStatus = rental.displayStatus || rental.status;
|
||||||
|
if (effectiveStatus !== "pending" && effectiveStatus !== "confirmed") {
|
||||||
let errorMessage = "This rental cannot be cancelled";
|
let errorMessage = "This rental cannot be cancelled";
|
||||||
if (rental.status === "active") {
|
if (effectiveStatus === "active") {
|
||||||
errorMessage = "Cannot cancel rental - the rental period has already started";
|
errorMessage = "Cannot cancel rental - the rental period has already started";
|
||||||
} else if (rental.status === "completed") {
|
} else if (effectiveStatus === "completed") {
|
||||||
errorMessage = "Cannot cancel rental - the rental has already been completed";
|
errorMessage = "Cannot cancel rental - the rental has already been completed";
|
||||||
} else if (rental.status === "cancelled") {
|
} else if (rental.status === "cancelled") {
|
||||||
errorMessage = "This rental has already been cancelled";
|
errorMessage = "This rental has already been cancelled";
|
||||||
|
|||||||
@@ -318,13 +318,16 @@ const Owning: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Filter owner rentals - exclude cancelled (shown in Rental History)
|
// Filter owner rentals - exclude cancelled (shown in Rental History)
|
||||||
|
// Use displayStatus for filtering/sorting as it includes computed "active" status
|
||||||
const allOwnerRentals = ownerRentals
|
const allOwnerRentals = ownerRentals
|
||||||
.filter((r) => ["pending", "confirmed", "active"].includes(r.status))
|
.filter((r) => ["pending", "confirmed", "active"].includes(r.displayStatus || r.status))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
const statusOrder = { pending: 0, confirmed: 1, active: 2 };
|
const statusOrder = { pending: 0, confirmed: 1, active: 2 };
|
||||||
|
const aStatus = a.displayStatus || a.status;
|
||||||
|
const bStatus = b.displayStatus || b.status;
|
||||||
return (
|
return (
|
||||||
statusOrder[a.status as keyof typeof statusOrder] -
|
statusOrder[aStatus as keyof typeof statusOrder] -
|
||||||
statusOrder[b.status as keyof typeof statusOrder]
|
statusOrder[bStatus as keyof typeof statusOrder]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -401,17 +404,17 @@ const Owning: React.FC = () => {
|
|||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<span
|
<span
|
||||||
className={`badge ${
|
className={`badge ${
|
||||||
rental.status === "active"
|
(rental.displayStatus || rental.status) === "active"
|
||||||
? "bg-success"
|
? "bg-success"
|
||||||
: rental.status === "pending"
|
: (rental.displayStatus || rental.status) === "pending"
|
||||||
? "bg-warning"
|
? "bg-warning"
|
||||||
: rental.status === "confirmed"
|
: (rental.displayStatus || rental.status) === "confirmed"
|
||||||
? "bg-info"
|
? "bg-info"
|
||||||
: "bg-danger"
|
: "bg-danger"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{rental.status.charAt(0).toUpperCase() +
|
{(rental.displayStatus || rental.status).charAt(0).toUpperCase() +
|
||||||
rental.status.slice(1)}
|
(rental.displayStatus || rental.status).slice(1)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -512,7 +515,7 @@ const Owning: React.FC = () => {
|
|||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{rental.status === "confirmed" && (
|
{(rental.displayStatus || rental.status) === "confirmed" && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-outline-danger"
|
className="btn btn-sm btn-outline-danger"
|
||||||
onClick={() => handleCancelClick(rental)}
|
onClick={() => handleCancelClick(rental)}
|
||||||
@@ -520,7 +523,7 @@ const Owning: React.FC = () => {
|
|||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{rental.status === "active" && (
|
{(rental.displayStatus || rental.status) === "active" && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-success"
|
className="btn btn-sm btn-success"
|
||||||
onClick={() => handleCompleteClick(rental)}
|
onClick={() => handleCompleteClick(rental)}
|
||||||
|
|||||||
@@ -182,8 +182,9 @@ const Renting: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Filter rentals - show only active rentals (declined go to history)
|
// Filter rentals - show only active rentals (declined go to history)
|
||||||
|
// Use displayStatus for filtering as it includes computed "active" status
|
||||||
const renterActiveRentals = rentals.filter((r) =>
|
const renterActiveRentals = rentals.filter((r) =>
|
||||||
["pending", "confirmed", "active"].includes(r.status)
|
["pending", "confirmed", "active"].includes(r.displayStatus || r.status)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -268,25 +269,25 @@ const Renting: React.FC = () => {
|
|||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<span
|
<span
|
||||||
className={`badge ${
|
className={`badge ${
|
||||||
rental.status === "active"
|
(rental.displayStatus || rental.status) === "active"
|
||||||
? "bg-success"
|
? "bg-success"
|
||||||
: rental.status === "pending"
|
: (rental.displayStatus || rental.status) === "pending"
|
||||||
? "bg-warning"
|
? "bg-warning"
|
||||||
: rental.status === "confirmed"
|
: (rental.displayStatus || rental.status) === "confirmed"
|
||||||
? "bg-info"
|
? "bg-info"
|
||||||
: rental.status === "declined"
|
: (rental.displayStatus || rental.status) === "declined"
|
||||||
? "bg-secondary"
|
? "bg-secondary"
|
||||||
: "bg-danger"
|
: "bg-danger"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{rental.status === "pending"
|
{(rental.displayStatus || rental.status) === "pending"
|
||||||
? "Awaiting Owner Approval"
|
? "Awaiting Owner Approval"
|
||||||
: rental.status === "confirmed"
|
: (rental.displayStatus || rental.status) === "confirmed"
|
||||||
? "Confirmed & Paid"
|
? "Confirmed & Paid"
|
||||||
: rental.status === "declined"
|
: (rental.displayStatus || rental.status) === "declined"
|
||||||
? "Declined by Owner"
|
? "Declined by Owner"
|
||||||
: rental.status.charAt(0).toUpperCase() +
|
: (rental.displayStatus || rental.status).charAt(0).toUpperCase() +
|
||||||
rental.status.slice(1)}
|
(rental.displayStatus || rental.status).slice(1)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -362,8 +363,8 @@ const Renting: React.FC = () => {
|
|||||||
|
|
||||||
<div className="d-flex flex-column gap-2 mt-3">
|
<div className="d-flex flex-column gap-2 mt-3">
|
||||||
<div className="d-flex gap-2">
|
<div className="d-flex gap-2">
|
||||||
{(rental.status === "pending" ||
|
{((rental.displayStatus || rental.status) === "pending" ||
|
||||||
rental.status === "confirmed") && (
|
(rental.displayStatus || rental.status) === "confirmed") && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-danger"
|
className="btn btn-sm btn-danger"
|
||||||
onClick={() => handleCancelClick(rental)}
|
onClick={() => handleCancelClick(rental)}
|
||||||
@@ -371,7 +372,7 @@ const Renting: React.FC = () => {
|
|||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{rental.status === "active" &&
|
{(rental.displayStatus || rental.status) === "active" &&
|
||||||
!rental.itemRating &&
|
!rental.itemRating &&
|
||||||
!rental.itemReviewSubmittedAt && (
|
!rental.itemReviewSubmittedAt && (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -106,6 +106,18 @@ export interface Item {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RentalStatus =
|
||||||
|
| "pending"
|
||||||
|
| "confirmed"
|
||||||
|
| "declined"
|
||||||
|
| "active"
|
||||||
|
| "completed"
|
||||||
|
| "cancelled"
|
||||||
|
| "returned_late"
|
||||||
|
| "returned_late_and_damaged"
|
||||||
|
| "damaged"
|
||||||
|
| "lost";
|
||||||
|
|
||||||
export interface Rental {
|
export interface Rental {
|
||||||
id: string;
|
id: string;
|
||||||
itemId: string;
|
itemId: string;
|
||||||
@@ -117,17 +129,9 @@ export interface Rental {
|
|||||||
// Fee tracking fields
|
// Fee tracking fields
|
||||||
platformFee?: number;
|
platformFee?: number;
|
||||||
payoutAmount?: number;
|
payoutAmount?: number;
|
||||||
status:
|
status: RentalStatus;
|
||||||
| "pending"
|
// Computed status (includes "active" when confirmed + start time passed)
|
||||||
| "confirmed"
|
displayStatus?: RentalStatus;
|
||||||
| "declined"
|
|
||||||
| "active"
|
|
||||||
| "completed"
|
|
||||||
| "cancelled"
|
|
||||||
| "returned_late"
|
|
||||||
| "returned_late_and_damaged"
|
|
||||||
| "damaged"
|
|
||||||
| "lost";
|
|
||||||
paymentStatus: "pending" | "paid" | "refunded";
|
paymentStatus: "pending" | "paid" | "refunded";
|
||||||
// Refund tracking fields
|
// Refund tracking fields
|
||||||
refundAmount?: number;
|
refundAmount?: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user