Employees can apply for leave and track balance, status, and history. Managers can view all requests and approve/reject them. Login with separate dashboards for employees and managers with the dummy json. Tables with filters (All, Pending, Approved, Rejected) for easy management.
149 lines
4.6 KiB
TypeScript
149 lines
4.6 KiB
TypeScript
export interface LeaveRequest {
|
|
id: number;
|
|
userId: number;
|
|
userName?: string;
|
|
type: "Sick" | "Casual" | "Annual" | "Unpaid";
|
|
fromDate: string;
|
|
toDate: string;
|
|
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<LeaveRequest[]> {
|
|
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<LeaveRequest[]> {
|
|
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();
|
|
|
|
// Calculate days
|
|
const from = new Date(data.fromDate);
|
|
const to = new Date(data.toDate);
|
|
const days = Math.ceil((to.getTime() - from.getTime()) / 86400000) + 1;
|
|
|
|
// Check balance
|
|
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) ?? "[]");
|
|
const newLeave: LeaveRequest = {
|
|
id: Date.now(),
|
|
userId,
|
|
type: data.type,
|
|
fromDate: data.fromDate,
|
|
toDate: data.toDate,
|
|
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<void> {
|
|
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
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
} |