Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69624f859c | ||
|
|
53e72e2a19 | ||
|
|
043c24a417 | ||
|
|
629e4f8ae8 | ||
|
|
e2df35929f |
No files matched your search
+16
-7
@@ -1,5 +1,5 @@
|
|||||||
const getRates = require("../util/exchangeRate");
|
const getRates = require("../util/exchangeRate");
|
||||||
const { calculatePlanProgress, updatePlanProgressInDB } = require("../util/calculations");
|
const { calculateProgress, updatePlanProgressInDB } = require("../util/calculations");
|
||||||
const suggestions = require("../util/suggestions");
|
const suggestions = require("../util/suggestions");
|
||||||
const status = require("../util/statuses");
|
const status = require("../util/statuses");
|
||||||
const ObjectId = require('mongodb').ObjectId;
|
const ObjectId = require('mongodb').ObjectId;
|
||||||
@@ -86,13 +86,19 @@ module.exports = (middleware, users, plans, assets) => {
|
|||||||
toCurrencyRates: [],
|
toCurrencyRates: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let planSchema = await plans.find({ userId: new ObjectId(user) }).project({
|
const userPlansFromDB = await plans.find({ userId: new ObjectId(req.session.userId) }).toArray();
|
||||||
name: 1, retirementAssets: 1, progress: 1, _id: 1,
|
|
||||||
}).toArray();
|
for (const plan of userPlansFromDB) {
|
||||||
|
const progress = await calculateProgress(plan, assets, users, req.session.user._id);
|
||||||
|
await updatePlanProgressInDB(plan._id, progress.percentage, plans);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedUserPlans = await plans.find({ userId: new ObjectId(req.session.user._id) }).toArray();
|
||||||
|
|
||||||
res.render('dashboard', {
|
res.render('dashboard', {
|
||||||
user: req.session.user,
|
user: req.session.user,
|
||||||
geoData: req.session.geoData,
|
geoData: req.session.geoData,
|
||||||
plans: planSchema, });
|
plans: updatedUserPlans, });
|
||||||
|
|
||||||
return res.status(status.Ok);
|
return res.status(status.Ok);
|
||||||
});
|
});
|
||||||
@@ -114,8 +120,8 @@ module.exports = (middleware, users, plans, assets) => {
|
|||||||
const userPlansFromDB = await plans.find({ userId: new ObjectId(req.session.userId) }).toArray();
|
const userPlansFromDB = await plans.find({ userId: new ObjectId(req.session.userId) }).toArray();
|
||||||
|
|
||||||
for (const plan of userPlansFromDB) {
|
for (const plan of userPlansFromDB) {
|
||||||
const percentage = await calculatePlanProgress(plan, assets, req.session.user._id);
|
const progress = await calculateProgress(plan, assets, users, req.session.user._id);
|
||||||
await updatePlanProgressInDB(plan._id, percentage, plans);
|
await updatePlanProgressInDB(plan._id, progress.percentage, plans);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedUserPlans = await plans.find({ userId: new ObjectId(req.session.user._id) }).toArray();
|
const updatedUserPlans = await plans.find({ userId: new ObjectId(req.session.user._id) }).toArray();
|
||||||
@@ -151,12 +157,15 @@ module.exports = (middleware, users, plans, assets) => {
|
|||||||
req.session.errMessage = "Plan not found or you do not have permission to view it.";
|
req.session.errMessage = "Plan not found or you do not have permission to view it.";
|
||||||
return res.status(status.NotFound).redirect('/plans');
|
return res.status(status.NotFound).redirect('/plans');
|
||||||
}
|
}
|
||||||
|
const progress = await calculateProgress(plan, assets, users, req.session.userId);
|
||||||
|
|
||||||
|
|
||||||
res.render('planDetail', {
|
res.render('planDetail', {
|
||||||
user: req.session.user,
|
user: req.session.user,
|
||||||
plan: plan,
|
plan: plan,
|
||||||
geoData: req.session.geoData,
|
geoData: req.session.geoData,
|
||||||
assets: userAssets,
|
assets: userAssets,
|
||||||
|
progress: progress,
|
||||||
suggestions: await suggestions.generateSuggestions(),
|
suggestions: await suggestions.generateSuggestions(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -42,4 +42,72 @@ async function updatePlanProgressInDB(planId, percentage, plans) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { calculatePlanProgress, updatePlanProgressInDB };
|
|
||||||
|
async function calculateProgress(plan, assets, users, userId) {
|
||||||
|
if (!plan || typeof plan !== 'object') {
|
||||||
|
console.error("Error with the plan");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!assets || typeof assets.find !== 'function') {
|
||||||
|
console.error("Error with the assets collection");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!users || typeof users.findOne !== 'function') {
|
||||||
|
console.error("Error with the users collection");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!userId) {
|
||||||
|
console.error("No user ID provided");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userAssets = await assets.find({ userId: new ObjectId(userId) }).toArray();
|
||||||
|
const totalUserAssetValue = userAssets.reduce((total, asset) => total + asset.value, 0);
|
||||||
|
const totalUserPlanValue = plan.retirementAssets;
|
||||||
|
|
||||||
|
const userDoc = await users.findOne({ _id: new ObjectId(userId) });
|
||||||
|
if (!userDoc || !userDoc.dob) {
|
||||||
|
console.error("User document or DOB not found for userId:", userId);
|
||||||
|
return { monthlyInvestment: NaN, totalCostOfRetirement: NaN, monthsUntilRetirement: NaN, yearsRetired: NaN, percentage: NaN };
|
||||||
|
}
|
||||||
|
const userDob = new Date(userDoc.dob);
|
||||||
|
|
||||||
|
if (isNaN(userDob.getTime())) {
|
||||||
|
console.error("userDob is an invalid date. Aborting calculation.");
|
||||||
|
return { monthlyInvestment: NaN, totalCostOfRetirement: NaN, monthsUntilRetirement: NaN, yearsRetired: NaN, percentage: NaN };
|
||||||
|
}
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
const userUnalivedBy = new Date(userDob);
|
||||||
|
userUnalivedBy.setFullYear(userUnalivedBy.getFullYear() + 90);
|
||||||
|
const yearOfRetirement = userDob.getFullYear() + plan.retirementAge;
|
||||||
|
const monthsUntilRetirement = (yearOfRetirement - today.getFullYear()) * 12;
|
||||||
|
const yearsRetired = userUnalivedBy.getFullYear() - yearOfRetirement;
|
||||||
|
|
||||||
|
const totalCostOfRetirement = ((plan.retirementExpenses + plan.retirementLiabilities) * 12) * yearsRetired;
|
||||||
|
|
||||||
|
const monthlyInvestment = (totalUserPlanValue - totalUserAssetValue + totalCostOfRetirement) / monthsUntilRetirement;
|
||||||
|
|
||||||
|
const percentageCalculated = (totalUserAssetValue / (totalUserPlanValue + totalCostOfRetirement)) * 100;
|
||||||
|
|
||||||
|
const progress = {};
|
||||||
|
|
||||||
|
progress.monthlyInvestment = Math.round(monthlyInvestment);
|
||||||
|
progress.totalCostOfRetirement = totalCostOfRetirement;
|
||||||
|
progress.monthsUntilRetirement = monthsUntilRetirement;
|
||||||
|
progress.yearsRetired = yearsRetired;
|
||||||
|
progress.yearsUntilRetirement = (yearOfRetirement - today.getFullYear());
|
||||||
|
progress.percentage = percentageCalculated;
|
||||||
|
|
||||||
|
return progress;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error in calculateProgress:", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { calculatePlanProgress, updatePlanProgressInDB, calculateProgress};
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
<%- include("./partials/header") %>
|
<%- include("./partials/header") %>
|
||||||
|
|
||||||
<main class="container mx-auto p-4 pt-30">
|
<main class="container mx-auto p-4 pt-30">
|
||||||
<h2 class="text-xl text-white font-semibold mb-4">Welcome: <%= user.name %></h2>
|
|
||||||
<div class="max-w-md mx-auto bg-white p-8 mb-10 rounded-lg shadow-md">
|
<div class="max-w-md mx-auto bg-white p-8 mb-10 rounded-lg shadow-md">
|
||||||
<h3 class="text-lg font-medium mb-6">Retirement Plan</h3>
|
<h3 class="text-lg font-medium mb-6">Retirement Plan</h3>
|
||||||
<form action="/newPlan" method="post" class="space-y-4">
|
<form action="/newPlan" method="post" class="space-y-4">
|
||||||
|
|||||||
@@ -22,11 +22,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<strong class="text-green-500">
|
<strong class="text-green-500">
|
||||||
<div class="float-right mb-2">
|
<div class="float-right mb-2">
|
||||||
<%=Math.floor(plan.progress) %>% complete
|
<%=Math.floor(plan.progress) > 100 ? 100 : Math.floor(plan.progress) %>% complete
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-400 mt-7">
|
<div class="bg-gray-400 mt-7">
|
||||||
<div class="bg-green-600 h-2.5 rounded-full dark:bg-green-500"
|
<div class="bg-green-600 h-2.5 rounded-full dark:bg-green-500"
|
||||||
style="width: <%= plan.progress %>%;"></div>
|
style="width: <%= plan.progress > 100 ? 100 : plan.progress %>%;"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+33
-12
@@ -1,10 +1,8 @@
|
|||||||
<%- include("./partials/fileHeader") %>
|
<%- include("./partials/fileHeader") %>
|
||||||
<%- include("./partials/header") %>
|
<%- include("./partials/header") %>
|
||||||
|
|
||||||
<main class="container mx-auto p-4 p-28">
|
<main class="container mx-auto p-4 pt-28">
|
||||||
<h2 class="text-xl text-white font-semibold mb-4">Welcome: <%= user.name %></h2>
|
|
||||||
<div class="max-w-lg mx-auto bg-white p-6 sm:p-8 rounded-xl shadow-lg space-y-6 dark:bg-gray-800">
|
<div class="max-w-lg mx-auto bg-white p-6 sm:p-8 rounded-xl shadow-lg space-y-6 dark:bg-gray-800">
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="text-2xl font-bold text-gray-800 dark:text-white"><%= plan.name %></h3>
|
<h3 class="text-2xl font-bold text-gray-800 dark:text-white"><%= plan.name %></h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -12,10 +10,17 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex justify-between mb-1">
|
<div class="flex justify-between mb-1">
|
||||||
<span class="text-sm font-medium text-blue-700 dark:text-blue-400">Progress</span>
|
<span class="text-sm font-medium text-blue-700 dark:text-blue-400">Progress</span>
|
||||||
<span class="text-sm font-medium text-blue-700 dark:text-blue-400"><%= plan.progress %>%</span>
|
<span class="text-sm font-medium text-blue-700 dark:text-blue-400"><%-plan.progress > 100 ? 100 : plan.progress%>%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full bg-gray-200 rounded-full h-3 dark:bg-gray-700">
|
<div class="w-full bg-gray-200 rounded-full h-3 dark:bg-gray-700">
|
||||||
<div class="bg-blue-600 h-3 rounded-full dark:bg-blue-500" style="width: <%= plan.progress %>%;"></div>
|
<div class="bg-blue-600 h-3 rounded-full dark:bg-blue-500" style="width: <%= plan.progress > 100 ? 100 : plan.progress %>%;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between mb-1">
|
||||||
|
<span class="text-sm font-medium text-blue-700 dark:text-blue-400">Calculated Monthly Investment</span>
|
||||||
|
<span class="text-sm font-medium text-blue-700 dark:text-blue-400"><%- new Intl.NumberFormat('en-US', { style: 'currency', currency: geoData.currency ? geoData.currency : 'CAD' }).format(progress.monthlyInvestment > 0 ? progress.monthlyInvestment : 0) %></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -29,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-600 dark:text-gray-400">Total Value:</label>
|
<label class="block text-sm font-medium text-gray-600 dark:text-gray-400">Total Value:</label>
|
||||||
<p class="mt-1 text-md text-gray-900 dark:text-white"><%= assets.reduce((total, asset) => total + asset.value, 0) %></p>
|
<p class="mt-1 text-md text-gray-900 dark:text-white"><%- new Intl.NumberFormat('en-US', { style: 'currency', currency: geoData.currency ? geoData.currency : 'CAD' }).format(assets.reduce((total, asset) => total + asset.value, 0)) %></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,16 +48,32 @@
|
|||||||
<p class="mt-1 text-md text-gray-900 dark:text-white"><%= plan.retirementAge %></p>
|
<p class="mt-1 text-md text-gray-900 dark:text-white"><%= plan.retirementAge %></p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-600 dark:text-gray-400">Target Monthly Expenses:</label>
|
<label class="block text-sm font-medium text-gray-600 dark:text-gray-400">Target Monthly Expenses (Minus Liabilities):</label>
|
||||||
<p class="mt-1 text-md text-gray-900 dark:text-white"><%= plan.retirementExpenses %></p>
|
<p class="mt-1 text-md text-gray-900 dark:text-white"><%- new Intl.NumberFormat('en-US', { style: 'currency', currency: geoData.currency ? geoData.currency : 'CAD' }).format(plan.retirementExpenses) %></p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-600 dark:text-gray-400">Target Retirement Assets:</label>
|
<label class="block text-sm font-medium text-gray-600 dark:text-gray-400">Target Retirement Assets (Total):</label>
|
||||||
<p class="mt-1 text-md text-gray-900 dark:text-white"><%= plan.retirementAssets %></p>
|
<p class="mt-1 text-md text-gray-900 dark:text-white"><%- new Intl.NumberFormat('en-US', { style: 'currency', currency: geoData.currency ? geoData.currency : 'CAD' }).format(plan.retirementAssets) %></p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-600 dark:text-gray-400">Target Retirement Liabilities:</label>
|
<label class="block text-sm font-medium text-gray-600 dark:text-gray-400">Target Retirement Liabilities (Monthly):</label>
|
||||||
<p class="mt-1 text-md text-gray-900 dark:text-white"><%= plan.retirementLiabilities %></p>
|
<p class="mt-1 text-md text-gray-900 dark:text-white"><%- new Intl.NumberFormat('en-US', { style: 'currency', currency: geoData.currency ? geoData.currency : 'CAD' }).format(plan.retirementLiabilities) %></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-600 dark:text-gray-400">Years Until Retirement:</label>
|
||||||
|
<p class="mt-1 text-md text-gray-900 dark:text-white"><%- progress.yearsUntilRetirement %></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-600 dark:text-gray-400">Years Retired:</label>
|
||||||
|
<p class="mt-1 text-md text-gray-900 dark:text-white"><%- progress.yearsRetired %></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-600 dark:text-gray-400">Total Cost of Retirement:</label>
|
||||||
|
<p class="mt-1 text-md text-gray-900 dark:text-white"><%- new Intl.NumberFormat('en-US', { style: 'currency', currency: geoData.currency ? geoData.currency : 'CAD' }).format(progress.totalCostOfRetirement) %></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-600 dark:text-gray-400">Total amount needed (Assets + Retirement Expenses): </label>
|
||||||
|
<p class="mt-1 text-md text-gray-900 dark:text-white"><%- new Intl.NumberFormat('en-US', { style: 'currency', currency: geoData.currency ? geoData.currency : 'CAD' }).format(progress.totalCostOfRetirement + progress.monthlyInvestment * progress.monthsUntilRetirement) %></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+1
-2
@@ -2,7 +2,6 @@
|
|||||||
<%- include("./partials/header") %>
|
<%- include("./partials/header") %>
|
||||||
|
|
||||||
<main class="container mx-auto p-4 pt-28">
|
<main class="container mx-auto p-4 pt-28">
|
||||||
<h2 class="text-xl text-white font-semibold mb-4">Welcome: <%= user.name %></h2>
|
|
||||||
<div class="max-w-md mx-auto bg-white p-8 mb-10 rounded-lg shadow-md space-y-4">
|
<div class="max-w-md mx-auto bg-white p-8 mb-10 rounded-lg shadow-md space-y-4">
|
||||||
<h3 class="text-lg font-medium mb-6">My Retirement Plans</h3>
|
<h3 class="text-lg font-medium mb-6">My Retirement Plans</h3>
|
||||||
<a href="/newPlan" class="block max-w-sm p-2"><div class="text-center bg-blue-600 text-white py-1 w-full rounded-md hover:bg-blue-700 hover:text-white font-semibold">New Plan</div></a>
|
<a href="/newPlan" class="block max-w-sm p-2"><div class="text-center bg-blue-600 text-white py-1 w-full rounded-md hover:bg-blue-700 hover:text-white font-semibold">New Plan</div></a>
|
||||||
@@ -11,7 +10,7 @@
|
|||||||
<a href="/plans/<%= plan._id %>" class="block max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow-sm hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700">
|
<a href="/plans/<%= plan._id %>" class="block max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow-sm hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700">
|
||||||
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white"><%= plan.name %></h5>
|
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white"><%= plan.name %></h5>
|
||||||
<div class="w-full bg-black rounded-full h-2.5 mb-4 dark:bg-black">
|
<div class="w-full bg-black rounded-full h-2.5 mb-4 dark:bg-black">
|
||||||
<div class="bg-green-600 h-2.5 rounded-full dark:bg-green-500" style="width: <%= plan.progress %>%;"></div>
|
<div class="bg-green-600 h-2.5 rounded-full dark:bg-green-500" style="width: <%= plan.progress > 100 ? 100 : plan.progress %>%;"></div>
|
||||||
</div>
|
</div>
|
||||||
<p class="font-normal text-gray-700 dark:text-gray-400"><%= plan.description %></p>
|
<p class="font-normal text-gray-700 dark:text-gray-400"><%= plan.description %></p>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Reference in new issue
Block a user