export interface LeaveRequest { id: number; userId: number; userName?: string; 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; } export interface UserRecord { id: number; username: string; name: string; role: "employee" | "manager"; leaveBalance?: number; } const LEAVES_KEY = "lms_leaves"; const USERS_KEY = "lms_users"; // Seed from JSON on first load async function seedIfNeeded() { if (typeof window === "undefined") return; if (!localStorage.getItem(LEAVES_KEY)) { const mod = await import("@/data/leaves.json"); const raw = mod.default ?? mod; const leaves = Array.isArray(raw) ? raw : ((raw as any).leaves ?? []); localStorage.setItem(LEAVES_KEY, JSON.stringify(leaves)); } if (!localStorage.getItem(USERS_KEY)) { const mod = await import("@/data/users.json"); const raw = mod.default ?? mod; const users = Array.isArray(raw) ? raw : ((raw as any).users ?? []); localStorage.setItem(USERS_KEY, JSON.stringify(users)); } } export async function getLeaves(): Promise { await seedIfNeeded(); const raw = localStorage.getItem(LEAVES_KEY) ?? "[]"; const leaves: LeaveRequest[] = JSON.parse(raw); // Attach userName from users const users = getUsers(); return leaves.map((l) => { const u = users.find((u) => u.id === l.userId); return { ...l, userName: u?.name ?? "Unknown" }; }); } export function getUsers(): UserRecord[] { const raw = localStorage.getItem(USERS_KEY) ?? "[]"; return JSON.parse(raw); } export async function getLeavesForUser(userId: number): Promise { const all = await getLeaves(); return all.filter((l) => l.userId === userId); } export async function applyLeave( userId: number, data: { type: LeaveRequest["type"]; fromDate: string; toDate: string; reason: string } ): Promise<{ ok: boolean; error?: string }> { await seedIfNeeded(); // 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)." }; } // 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" }; 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], }; leaves.push(newLeave); localStorage.setItem(LEAVES_KEY, JSON.stringify(leaves)); return { ok: true }; } export async function updateLeaveStatus( leaveId: number, status: "Approved" | "Rejected" ): Promise { await seedIfNeeded(); const leaves: LeaveRequest[] = JSON.parse(localStorage.getItem(LEAVES_KEY) ?? "[]"); const idx = leaves.findIndex((l) => l.id === leaveId); if (idx === -1) return; const leave = leaves[idx]; const wasApproved = leave.status === "Approved"; leaves[idx] = { ...leave, status }; localStorage.setItem(LEAVES_KEY, JSON.stringify(leaves)); // 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 days = leave.days ?? daysBetween(leave.fromDate, leave.toDate); // 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)); // 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)); } } } } export function getUserBalance(userId: number): number { const users = getUsers(); const u = users.find((u) => u.id === userId); 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 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; }