Added working-day-only calculation, Fixed leave logic so balance is deducted only on approval and pending requests are considered to prevent over-application.

Ensured consistent day calculation using stored days field and removed duplicate daysBetween logic.
Implemented working-day-only calculation, excluding weekends (Saturday & Sunday), and blocked invalid date selections.
Corrected handling of Unpaid leave so it does not affect leave balance.
Added routing and integrated Sonner notifications for login, manager actions, and employee leave submission.
This commit is contained in:
sumona-banerjeee committed 2026-04-29 19:14:58 +05:30
1 parent 45444ba8bd
commit 96cc1bb684
9 files changed
+265 -183

No files matched your search

+66 -27
View File
@@ -5,6 +5,8 @@ export interface LeaveRequest {
type: "Sick" | "Casual" | "Annual" | "Unpaid";
fromDate: string;
toDate: string;
/** Immutable day count stored at apply-time; used for all balance calculations. */
days: number;
reason: string;
status: "Pending" | "Approved" | "Rejected";
appliedAt: string;
@@ -66,25 +68,46 @@ export async function applyLeave(
): Promise<{ ok: boolean; error?: string }> {
await seedIfNeeded();
// Calculate days
const from = new Date(data.fromDate);
const to = new Date(data.toDate);
const days = Math.ceil((to.getTime() - from.getTime()) / 86400000) + 1;
// Block if fromDate or toDate is a weekend
if (isWeekend(data.fromDate) || isWeekend(data.toDate)) {
return { ok: false, error: "Leave cannot start or end on a weekend (Saturday/Sunday)." };
}
// Check balance
// Calculate days using the shared util (no inline duplication)
const days = daysBetween(data.fromDate, data.toDate);
if (days === 0) {
return { ok: false, error: "Selected range has no working days." };
}
// Check balance — account for already-pending requests
const users = getUsers();
const user = users.find((u) => u.id === userId);
if (!user) return { ok: false, error: "User not found" };
if ((user.leaveBalance ?? 0) < days)
return { ok: false, error: `Insufficient balance. You have ${user.leaveBalance} days left.` };
const leaves: LeaveRequest[] = JSON.parse(localStorage.getItem(LEAVES_KEY) ?? "[]");
// Only check balance for non-Unpaid leave
if (data.type !== "Unpaid") {
const pendingDays = leaves
.filter((l) => l.userId === userId && l.status === "Pending" && l.type !== "Unpaid")
.reduce((sum, l) => sum + (l.days ?? daysBetween(l.fromDate, l.toDate)), 0);
const availableBalance = (user.leaveBalance ?? 0) - pendingDays;
if (availableBalance < days)
return {
ok: false,
error: `Insufficient balance. You have ${user.leaveBalance} total days, but ${pendingDays} are reserved by pending requests (${availableBalance} available).`,
};
}
const newLeave: LeaveRequest = {
id: Date.now(),
userId,
type: data.type,
fromDate: data.fromDate,
toDate: data.toDate,
days,
reason: data.reason,
status: "Pending",
appliedAt: new Date().toISOString().split("T")[0],
@@ -108,30 +131,32 @@ export async function updateLeaveStatus(
leaves[idx] = { ...leave, status };
localStorage.setItem(LEAVES_KEY, JSON.stringify(leaves));
// Adjust balance
// Adjust balance using the immutable `days` stored at apply-time.
// Fallback to daysBetween for legacy records that pre-date the `days` field.
const users = getUsers();
const user = users.find((u) => u.id === leave.userId);
if (!user || user.role !== "employee") return;
const from = new Date(leave.fromDate);
const to = new Date(leave.toDate);
const days = Math.ceil((to.getTime() - from.getTime()) / 86400000) + 1;
const days = leave.days ?? daysBetween(leave.fromDate, leave.toDate);
if (status === "Approved" && !wasApproved) {
user.leaveBalance = (user.leaveBalance ?? 0) - days;
} else if (status === "Rejected" && wasApproved) {
user.leaveBalance = (user.leaveBalance ?? 0) + days;
}
// Only adjust balance for non-Unpaid leave
if (leave.type !== "Unpaid") {
if (status === "Approved" && !wasApproved) {
user.leaveBalance = (user.leaveBalance ?? 0) - days;
} else if (status === "Rejected" && wasApproved) {
user.leaveBalance = (user.leaveBalance ?? 0) + days;
}
localStorage.setItem(USERS_KEY, JSON.stringify(users));
localStorage.setItem(USERS_KEY, JSON.stringify(users));
// Also update session user if it's the same person
const sessionUser = localStorage.getItem("lms_user");
if (sessionUser) {
const su = JSON.parse(sessionUser);
if (su.id === user.id) {
su.leaveBalance = user.leaveBalance;
localStorage.setItem("lms_user", JSON.stringify(su));
// Also update session user if it's the same person
const sessionUser = localStorage.getItem("lms_user");
if (sessionUser) {
const su = JSON.parse(sessionUser);
if (su.id === user.id) {
su.leaveBalance = user.leaveBalance;
localStorage.setItem("lms_user", JSON.stringify(su));
}
}
}
}
@@ -142,8 +167,22 @@ export function getUserBalance(userId: number): number {
return u?.leaveBalance ?? 0;
}
// Returns true if date is Saturday or Sunday
export function isWeekend(dateStr: string): boolean {
const day = new Date(dateStr).getDay();
return day === 0 || day === 6;
}
// Count only working days (Mon–Fri) between two dates
export function daysBetween(from: string, to: string): number {
const f = new Date(from);
const t = new Date(to);
return Math.ceil((t.getTime() - f.getTime()) / 86400000) + 1;
const start = new Date(from);
const end = new Date(to);
let count = 0;
const current = new Date(start);
while (current <= end) {
const day = current.getDay();
if (day !== 0 && day !== 6) count++;
current.setDate(current.getDate() + 1);
}
return count;
}