import React, { useEffect, useMemo, useState } from "react";
import { createRoot } from "react-dom/client";
import { AnimatePresence, motion } from "framer-motion";
import {
Bell,
Building2,
Camera,
CalendarDays,
Check,
ChevronRight,
Clock3,
CreditCard,
Download,
FileText,
HeartPulse,
Home,
LogOut,
MapPin,
Menu,
Plus,
QrCode,
Search,
Settings,
ShieldCheck,
Users,
X,
} from "lucide-react";
import { BrowserRouter, Navigate, Route, Routes, useNavigate } from "react-router-dom";
import { Area, AreaChart, Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
import "./index.css";
const API_BASE = import.meta.env.VITE_API_BASE || "";
const appVersion = "NeverLag Work v1.0.0";
const demoUsers = [
{ label: "Super Admin", username: "super", password: "admin123" },
{ label: "Company Admin / Boss", username: "boss", password: "boss123" },
{ label: "Branch Manager", username: "manager", password: "manager123" },
{ label: "Employee", username: "employee", password: "employee123" },
{ label: "QR/Selfie Test Employee", username: "tester", password: "tester123" },
];
const navByRole = {
super_admin: [
["Home", Home],
["Companies", Building2],
["Plans", CreditCard],
["Reports", FileText],
["More", Menu],
],
boss: [
["Home", Home],
["Branches", Building2],
["Employees", Users],
["Attendance", CalendarDays],
["Salary", CreditCard],
["More", Menu],
],
branch_manager: [
["Home", Home],
["Employees", Users],
["Attendance", CalendarDays],
["Salary", CreditCard],
["More", Menu],
],
employee: [
["Home", Home],
["Attendance", CalendarDays],
["Salary Slip", FileText],
["Notifications", Bell],
["Profile", Users],
],
};
function getNavForRole(role, showBranchTools) {
const nav = navByRole[role] || [];
if (!showBranchTools) return nav.filter(([name]) => name !== "Branches");
return nav;
}
const statColors = {
green: "bg-blue-50 text-primary",
blue: "bg-blue-50 text-blue-700",
orange: "bg-orange-50 text-orange-700",
red: "bg-red-50 text-red-700",
gray: "bg-slate-100 text-slate-600",
};
function HeroIcon({ name, className = "h-6 w-6" }) {
const paths = {
speed: "M3.75 13.5a8.25 8.25 0 1 1 16.5 0c0 2.1-.78 4.02-2.07 5.48a.75.75 0 0 1-.56.25H6.38a.75.75 0 0 1-.56-.25A8.22 8.22 0 0 1 3.75 13.5Zm8.25 0 3.75-4.5",
shield: "M12 3.75 5.25 6v5.08c0 4.18 2.8 7.84 6.75 8.92 3.95-1.08 6.75-4.74 6.75-8.92V6L12 3.75Z",
cloud: "M6.75 18.75h10.5a4.5 4.5 0 0 0 .5-8.97 6 6 0 0 0-11.44-1.66A5.25 5.25 0 0 0 6.75 18.75Z",
lock: "M7.5 10.5V8.25a4.5 4.5 0 0 1 9 0v2.25m-10.5 0h12a.75.75 0 0 1 .75.75v7.5a.75.75 0 0 1-.75.75h-12a.75.75 0 0 1-.75-.75v-7.5a.75.75 0 0 1 .75-.75Z",
};
return (
);
}
async function api(path, options = {}) {
const token = sessionStorage.getItem("authToken");
const res = await fetch(`${API_BASE}${path}`, {
method: options.method || "GET",
headers: {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: options.body ? JSON.stringify(options.body) : undefined,
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.error || "Data was not saved. Please check server/database connection.");
return data;
}
function App() {
return (
} />
} />
);
}
function Login() {
const navigate = useNavigate();
const [form, setForm] = useState({ username: "boss", password: "boss123" });
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
async function submit(event) {
event.preventDefault();
setLoading(true);
setError("");
try {
const data = await api("/api/auth/login", { method: "POST", body: form });
sessionStorage.setItem("authToken", data.token);
sessionStorage.setItem("currentUser", JSON.stringify(data.user));
navigate("/");
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
return (
NeverLag Work
Employee Attendance & Payroll SaaS
{[
["Speed", "Live attendance flow", "speed"],
["Security", "GPS, selfie, QR checks", "lock"],
["Cloud", "Branch-ready SaaS", "cloud"],
].map(([title, meta, icon]) => (
{title}
{meta}
))}
Login
Role is detected after login.
setForm({ ...form, username: value })} />
setForm({ ...form, password: value })} />
{error && {error}
}
{loading ? "Signing in..." : "Login"}
{demoUsers.map((user) => (
setForm({ username: user.username, password: user.password })}
className="secondary-pill justify-between text-left text-sm"
>
{user.label}
))}
);
}
function AnimatedField({ label, type = "text", value, onChange }) {
return (
{label}
onChange(event.target.value)}
/>
);
}
function Shell() {
const navigate = useNavigate();
const [user, setUser] = useState(() => safeJson(sessionStorage.getItem("currentUser")));
const [active, setActive] = useState("Home");
const [data, setData] = useState(null);
const [toast, setToast] = useState("");
const [showNotifications, setShowNotifications] = useState(false);
const showBranchTools = Boolean(data?.settings?.enableMultiBranch);
const nav = getNavForRole(user?.role, showBranchTools);
useEffect(() => {
if (!sessionStorage.getItem("authToken")) navigate("/login");
}, [navigate]);
useEffect(() => {
if (!user) return;
refresh().catch((err) => setToast(err.message));
}, [user]);
useEffect(() => {
if (active === "Branches" && !showBranchTools) setActive("Home");
}, [active, showBranchTools]);
async function refresh() {
const [me, attendance, slips, notifications, settings] = await Promise.all([
api("/api/auth/me"),
api("/api/employee-attendance/today"),
api("/api/employee-salary/slips"),
api("/api/notifications"),
["boss", "branch_manager", "employee"].includes(user.role) ? api("/api/company/settings") : Promise.resolve({}),
]);
setUser(me.user);
sessionStorage.setItem("currentUser", JSON.stringify(me.user));
setData({ attendance, slips, notifications, settings });
}
async function logout() {
await api("/api/auth/logout", { method: "POST" }).catch(() => null);
sessionStorage.removeItem("authToken");
sessionStorage.removeItem("currentUser");
navigate("/login");
}
if (!user) return null;
return (
{nav.map(([name, Icon]) => (
setActive(name)} />
))}
{appVersion}
{renderPage({ user, active, setActive, data, refresh, setToast, showBranchTools })}
{nav.map(([name, Icon]) => (
setActive(name)} className="relative flex min-h-14 flex-col items-center justify-center gap-1 rounded-2xl px-2 py-2 text-xs font-bold text-muted">
{active === name && }
{name}
))}
{toast && setToast("")} />}
{showNotifications && setShowNotifications(false)} />}
);
}
function Brand() {
return (
NeverLag Work
Employee Attendance & Payroll SaaS
);
}
function NavButton({ name, Icon, active, onClick }) {
return (
{name}
);
}
function renderPage(props) {
if (props.user.role === "employee") return ;
if (["boss", "branch_manager"].includes(props.user.role)) return ;
return ;
}
function EmployeePages({ user, active, data, refresh, setToast }) {
if (active === "Attendance") return ;
if (active === "Salary Slip") return ;
if (active === "Notifications") return ;
if (active === "Profile") return ;
return ;
}
function BossPages({ active, data, refresh, setToast, setActive, showBranchTools }) {
if (active === "Branches") return showBranchTools ? : ;
if (active === "Employees") return ;
if (active === "Attendance") return ;
if (active === "Salary") return ;
if (active === "More") return ;
return ;
}
function SuperPages({ active, data }) {
if (active === "Companies") return ;
if (active === "Plans") return ;
if (active === "Reports") return ;
if (active === "More") return ;
return ;
}
function EmployeeHome({ user, data, refresh, setToast }) {
const today = data?.attendance?.rows?.[0];
const settings = data?.settings || {};
const checkedIn = Boolean(today?.checkInTime);
const checkedOut = Boolean(today?.checkOutTime);
const status = today?.status || "Absent";
const [selfie, setSelfie] = useState("");
const [qrToken, setQrToken] = useState("");
const [qrStatus, setQrStatus] = useState("");
const [showCamera, setShowCamera] = useState(false);
const [showQrScanner, setShowQrScanner] = useState(false);
const qrEnabled = Boolean(user?.username === "tester" || settings.enableQRAttendance || settings.qrRequiredOnCheckIn || settings.qrRequiredOnCheckOut);
async function verifyQrToken(token = qrToken) {
if (!token) {
setToast("Please scan office QR code to continue.");
return;
}
const verified = await api("/api/employee-attendance/verify-qr", { method: "POST", body: { qrToken: token } });
setQrToken(token);
setQrStatus(`QR verified for branch ${verified.branchId}`);
setToast("QR verified");
}
async function action(kind) {
try {
if ((kind === "in" && settings.selfieRequiredOnCheckIn || kind === "out" && settings.selfieRequiredOnCheckOut) && !selfie) {
setShowCamera(true);
throw new Error("Selfie required but not captured");
}
if ((kind === "in" && settings.qrRequiredOnCheckIn || kind === "out" && settings.qrRequiredOnCheckOut) && !qrToken) {
throw new Error("Please scan office QR code to continue.");
}
let selfieUrl = "";
if (selfie) {
const upload = await api("/api/uploads/selfie", { method: "POST", body: { dataUrl: selfie } });
selfieUrl = upload.url;
}
const position = await getLocation();
await api(`/api/employee-attendance/${kind === "in" ? "check-in" : "check-out"}`, {
method: "POST",
body: { latitude: position.lat, longitude: position.lng, qrToken, selfieUrl, deviceInfo: navigator.userAgent },
});
setSelfie("");
setQrToken("");
setQrStatus("");
setToast(kind === "in" ? "Check-in successful" : "Check-out successful");
await refresh();
} catch (err) {
setToast(err.message);
}
}
return (
{settings.selfieRequiredOnCheckIn || settings.selfieRequiredOnCheckOut ? : null}
{settings.qrRequiredOnCheckIn || settings.qrRequiredOnCheckOut ? : null}
{qrEnabled &&
{ setQrToken(value); setQrStatus(""); }} />
setShowQrScanner(true)} className="primary-pill gap-2 px-4"> Scan QR
verifyQrToken().catch((err) => setToast(err.message))} className="secondary-pill px-4">Verify QR
{user?.username === "tester" && verifyQrToken("MAIN-OFFICE-DEMO-QR").catch((err) => setToast(err.message))} className="secondary-pill px-4">Use Demo QR }
{qrStatus &&
{qrStatus}
}
}
{(settings.selfieRequiredOnCheckIn || settings.selfieRequiredOnCheckOut) &&
setShowCamera(true)} className="secondary-pill gap-2"> {selfie ? "Retake Selfie" : "Capture Selfie"}
{selfie &&
}
}
action(checkedIn ? "out" : "in")}
className={`mt-8 min-h-24 w-full rounded-full text-xl font-semibold shadow-lift transition hover:-translate-y-0.5 ${checkedOut ? "bg-slate-400 text-white" : checkedIn ? "bg-primary text-white hover:bg-[#74a2f7]" : "gradient-bg text-white shadow-glow"}`}
>
{checkedOut ? "Completed Today" : checkedIn ? "Check Out" : "Check In"}
{showCamera && setShowCamera(false)} onCapture={(image) => { setSelfie(image); setShowCamera(false); }} />}
{showQrScanner && setShowQrScanner(false)} onScan={(token) => { setShowQrScanner(false); verifyQrToken(token).catch((err) => setToast(err.message)); }} />}
);
}
function BossHome({ data, setToast, setActive, refresh, showBranchTools }) {
const [branches, setBranches] = useState([]);
const [branchId, setBranchId] = useState("");
const [attendance, setAttendance] = useState(data?.attendance || {});
const stats = attendance?.dashboard || {};
useEffect(() => setAttendance(data?.attendance || {}), [data]);
useEffect(() => {
api("/api/branches").then(setBranches).catch((err) => setToast?.(err.message));
}, [setToast]);
useEffect(() => {
api(`/api/employee-attendance/today${branchId ? `?branchId=${branchId}` : ""}`)
.then(setAttendance)
.catch((err) => setToast?.(err.message));
}, [branchId, setToast]);
return (
);
}
function AdvancedModuleShortcuts({ setActive, setToast, refresh, showBranchTools }) {
async function enableDemoTest() {
await api("/api/company/settings", {
method: "PUT",
body: {
enableSelfieAttendance: true,
selfieRequiredOnCheckIn: true,
selfieRequiredOnCheckOut: false,
enableQRAttendance: true,
qrRequiredOnCheckIn: true,
qrRequiredOnCheckOut: false,
enableMultiBranch: true,
},
});
setToast?.("QR + Selfie test mode enabled. Demo QR token: MAIN-OFFICE-DEMO-QR");
await refresh?.();
}
const modules = [
...(showBranchTools ? [["Branches", "Multiple offices, branch geofence, and branch status.", "Branches", Building2]] : []),
["Selfie Attendance", "Enable camera selfie rules from attendance settings.", "More", Camera],
["Attendance QR", "Generate, print, regenerate, and activate office QR codes.", "More", QrCode],
...(showBranchTools ? [["Branch Managers", "Create logins restricted to assigned branch data.", "More", Users]] : []),
["Premium Features", "Face Recognition and Biometric placeholders.", "More", ShieldCheck],
];
return (
{modules.map(([title, meta, target, Icon]) => (
setActive(target)} className="app-card p-4 text-left transition hover:-translate-y-1 hover:shadow-lift">
{title}
{meta}
))}
enableDemoTest().catch((err) => setToast?.(err.message))} className="app-card border-primary/25 bg-blue-50 p-4 text-left transition hover:-translate-y-1 hover:shadow-lift">
Enable Test Mode
QR + Selfie required, token MAIN-OFFICE-DEMO-QR.
);
}
function SuperHome({ data }) {
const stats = data?.attendance?.dashboard || {};
return (
);
}
function StatGrid({ stats }) {
return (
{stats.map(([label, value, color], index) => (
))}
);
}
function Counter({ value, className }) {
const [shown, setShown] = useState(0);
useEffect(() => {
const numeric = Number(value) || 0;
const id = setInterval(() => setShown((old) => (old >= numeric ? numeric : Math.min(numeric, old + Math.ceil(numeric / 12 || 1)))), 18);
return () => clearInterval(id);
}, [value]);
return {shown} ;
}
function AttendanceCards({ data, boss, refresh, setToast }) {
const rows = data?.attendance?.rows || [];
const [selectedMonth, setSelectedMonth] = useState(() => new Date().toISOString().slice(0, 7));
const [monthlyRows, setMonthlyRows] = useState(rows);
const [filter, setFilter] = useState("All");
useEffect(() => setMonthlyRows(rows), [rows]);
useEffect(() => {
api(`/api/employee-attendance/monthly?month=${selectedMonth}`)
.then(setMonthlyRows)
.catch((err) => setToast?.(err.message));
}, [selectedMonth, setToast]);
const filtered = filter === "All" ? monthlyRows : monthlyRows.filter((row) => attendanceFilterMatch(row, filter));
async function markLeave(row) {
await api("/api/leaves/mark", { method: "POST", body: { employeeId: row.employeeId, date: row.date } });
setToast?.("Leave marked");
await refresh?.();
}
async function correct(row) {
const reason = window.prompt("Correction reason");
if (!reason) return;
const status = window.prompt("New status: Present, Late, Half Day, Leave, Absent", row.status || "Present");
if (!status) return;
await api(`/api/employee-attendance/${row.id || row._id}/manual-correction`, { method: "PUT", body: { status, reason } });
setToast?.("Attendance corrected");
await refresh?.();
}
return (
{filtered.map((row, index) => (
{initials(row.employeeName || "Employee")}
{row.employeeName || "Employee"}
{row.department || "Department optional"}
{(row.checkInSelfieUrl || row.checkOutSelfieUrl) &&
{row.checkInSelfieUrl && }
{row.checkOutSelfieUrl && }
}
{boss &&
markLeave(row).catch((err) => setToast?.(err.message))} className="min-h-11 rounded-2xl bg-slate-100 px-4 font-black">Mark Leave
correct(row).catch((err) => setToast?.(err.message))} className="min-h-11 rounded-2xl bg-primary px-4 font-black text-white">Correct
}
))}
{!filtered.length &&
}
);
}
function AttendanceCalendar({ month, rows, filter, boss }) {
const days = calendarDays(month);
const recordsByDate = rows.reduce((map, row) => {
const key = row.date;
map[key] = map[key] || [];
map[key].push(row);
return map;
}, {});
return (
Attendance Calendar
{new Date(`${month}-01T00:00:00`).toLocaleDateString([], { month: "long", year: "numeric" })}
{attendanceLegend().map((item) => {item.label} )}
{["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((day) =>
{day}
)}
{days.map((day, index) => {
if (!day) return
;
const dayRows = recordsByDate[day.date] || [];
const cell = attendanceCell(day, dayRows, filter, boss);
return (
{day.dayNumber}
{cell.short}
{cell.title}
{cell.meta}
);
})}
);
}
function calendarDays(month) {
const [year, monthIndex] = month.split("-").map(Number);
const first = new Date(year, monthIndex - 1, 1);
const total = new Date(year, monthIndex, 0).getDate();
const blanks = Array.from({ length: first.getDay() }, () => null);
const days = Array.from({ length: total }, (_, index) => {
const dayNumber = index + 1;
const date = `${month}-${String(dayNumber).padStart(2, "0")}`;
return { date, dayNumber };
});
return [...blanks, ...days];
}
function attendanceFilterMatch(row, filter) {
if (filter === "All") return true;
if (filter === "Checked Out") return Boolean(row.checkOutTime);
if (filter === "Inside Office") return Boolean(row.checkInTime && !row.checkOutTime);
return (row.status || "Absent") === filter;
}
function attendancePalette(status) {
const normalized = status === "Half Day" ? "Late" : status;
const palette = {
Present: { short: "P", title: "Present", className: "border-emerald-200 bg-emerald-50 text-emerald-800", badgeClass: "bg-emerald-600 text-white", legendClass: "bg-emerald-50 text-emerald-700", dotClass: "bg-emerald-500" },
Absent: { short: "A", title: "Absent", className: "border-red-200 bg-red-50 text-red-800", badgeClass: "bg-red-600 text-white", legendClass: "bg-red-50 text-red-700", dotClass: "bg-red-500" },
Late: { short: "L", title: "Late", className: "border-amber-200 bg-amber-50 text-amber-800", badgeClass: "bg-amber-500 text-white", legendClass: "bg-amber-50 text-amber-700", dotClass: "bg-amber-500" },
Leave: { short: "LV", title: "Leave", className: "border-violet-200 bg-violet-50 text-violet-800", badgeClass: "bg-violet-600 text-white", legendClass: "bg-violet-50 text-violet-700", dotClass: "bg-violet-500" },
"Checked Out": { short: "OUT", title: "Checked Out", className: "border-sky-200 bg-sky-50 text-sky-800", badgeClass: "bg-sky-600 text-white", legendClass: "bg-sky-50 text-sky-700", dotClass: "bg-sky-500" },
"Inside Office": { short: "IN", title: "Inside Office", className: "border-blue-200 bg-blue-50 text-blue-800", badgeClass: "bg-primary text-white", legendClass: "bg-blue-50 text-blue-700", dotClass: "bg-primary" },
"No Record": { short: "-", title: "No Record", className: "border-line bg-white text-muted", badgeClass: "bg-surface text-muted", legendClass: "bg-slate-50 text-slate-600", dotClass: "bg-slate-300" },
};
return palette[normalized] || palette["No Record"];
}
function attendanceCell(day, rows, filter, boss) {
const todayKey = new Date().toISOString().slice(0, 10);
if (!rows.length) {
const isPastOrToday = day.date <= todayKey;
const generatedStatus = !boss && isPastOrToday ? "Absent" : "No Record";
const palette = attendancePalette(generatedStatus);
const hidden = filter !== "All" && filter !== generatedStatus;
return { ...palette, className: `${palette.className} ${hidden ? "opacity-30" : ""}`, meta: generatedStatus === "Absent" ? "No check-in" : "No data yet" };
}
const checkedOut = rows.filter((row) => row.checkOutTime).length;
const inside = rows.filter((row) => row.checkInTime && !row.checkOutTime).length;
const priority = rows.find((row) => row.status === "Leave") || rows.find((row) => row.status === "Late") || rows.find((row) => row.status === "Absent") || rows[0];
const status = checkedOut && rows.length === checkedOut ? "Checked Out" : inside && rows.length === inside ? "Inside Office" : priority.status || "Present";
const palette = attendancePalette(status);
const hidden = filter !== "All" && !rows.some((row) => attendanceFilterMatch(row, filter)) && !(filter === status);
return {
...palette,
className: `${palette.className} ${hidden ? "opacity-30" : ""}`,
title: boss ? `${rows.length} record${rows.length === 1 ? "" : "s"}` : palette.title,
meta: boss ? `${checkedOut} out / ${inside} inside` : `${time(priority.checkInTime)} - ${time(priority.checkOutTime)}`,
};
}
function attendanceLegend() {
return ["Present", "Absent", "Late", "Leave", "Checked Out", "Inside Office"].map((status) => ({ label: `${attendancePalette(status).short} ${status}`, ...attendancePalette(status) }));
}
function SelfieThumb({ label, src }) {
return {label}
;
}
function BranchFilter({ branches, value, onChange }) {
if (!branches || branches.length <= 1) return null;
return (
Branch Filter
onChange(event.target.value)}>
All Branches
{branches.map((branch) => {branch.branchName} )}
);
}
function EmployeesPage({ setToast, showBranchTools }) {
const [items, setItems] = useState([]);
const [branches, setBranches] = useState([]);
const [open, setOpen] = useState(false);
const [editing, setEditing] = useState(null);
async function load() {
const [employees, branchRows] = await Promise.all([api("/api/employees"), api("/api/branches")]);
setItems(employees);
setBranches(branchRows);
}
useEffect(() => {
load().catch((err) => setToast(err.message));
}, [setToast]);
async function removeEmployee(employee) {
await api(`/api/employees/${employee.id || employee._id}`, { method: "DELETE" });
setItems((old) => old.filter((item) => (item.id || item._id) !== (employee.id || employee._id)));
setToast("Employee deleted");
}
return (
);
}
function EmployeeCard({ employee, onEdit, onDelete }) {
return (
{initials(employee.name)}
{employee.name}
{employee.employeeCode} / {employee.mobile}
X
{employee.department || "Department optional"} / {employee.designation || "Designation optional"}
{employee.branchName || "Main Office"}
);
}
function EmployeeSheet({ initial, branches, showBranchTools, onClose, onSaved, setToast }) {
const defaultBranchId = branches?.[0]?.id || branches?.[0]?._id || "";
const [form, setForm] = useState(() => initial ? { ...initial, branchId: initial.branchId || defaultBranchId, username: initial.username || "", password: "" } : { name: "", employeeCode: "", mobile: "", email: "", department: "", designation: "", joiningDate: "", salary: "", shift: "General", branchId: defaultBranchId, username: "", password: "", status: "Active" });
const [errors, setErrors] = useState({});
async function save(event) {
event.preventDefault();
try {
const id = initial?.id || initial?._id;
const employee = await api(id ? `/api/employees/${id}` : "/api/employees", { method: id ? "PUT" : "POST", body: form });
onSaved(employee);
onClose();
} catch (err) {
setToast(err.message);
setErrors({ form: err.message });
}
}
return (
{initial ? "Edit Employee" : "Add Employee"}
{Object.keys(form).filter((key) => showBranchTools || key !== "branchId").map((key) => key === "branchId" ? (
Assigned Branch
setForm({ ...form, branchId: event.target.value })}>
Select Branch
{branches.map((branch) => {branch.branchName} )}
) : (
{key.replace(/([A-Z])/g, " $1")}
setForm({ ...form, [key]: event.target.value })} />
{errors[key] && {errors[key]} }
))}
{errors.form && {errors.form}
}
Save Employee
);
}
function SalaryPanel({ slips, refresh, setToast, showBranchTools }) {
const [branches, setBranches] = useState([]);
const [branchId, setBranchId] = useState("");
const [rows, setRows] = useState(slips);
useEffect(() => setRows(slips), [slips]);
useEffect(() => {
api("/api/branches").then(setBranches).catch((err) => setToast(err.message));
}, [setToast]);
useEffect(() => {
api(`/api/employee-salary/slips${branchId ? `?branchId=${branchId}` : ""}`).then(setRows).catch((err) => setToast(err.message));
}, [branchId, setToast]);
async function generate() {
try {
await api("/api/employee-salary/generate", { method: "POST", body: { branchId } });
setToast("Salary slip generated");
setRows(await api(`/api/employee-salary/slips${branchId ? `?branchId=${branchId}` : ""}`));
await refresh();
} catch (err) {
setToast(err.message);
}
}
async function toggleStatus(slip) {
const next = slip.paymentStatus === "Paid" ? "Pending" : "Paid";
await api(`/api/employee-salary/slips/${slip.id || slip._id}/status`, { method: "PUT", body: { paymentStatus: next } });
setToast(`Salary slip marked ${next}`);
await refresh();
}
return (
{showBranchTools && }
Generate Salary Slips
);
}
function SalarySlips({ slips, onToggleStatus }) {
return (
{slips.map((slip) => (
{slip.slipNumber}
{slip.month} {slip.year}
downloadSalaryPdf(slip)} className="inline-flex min-h-11 items-center gap-2 rounded-2xl bg-slate-100 px-5 py-3 font-black transition hover:-translate-y-0.5 hover:bg-slate-200">
Download PDF
{onToggleStatus && onToggleStatus(slip)} className="min-h-11 rounded-2xl bg-primary px-5 py-3 font-black text-white transition hover:-translate-y-0.5">Mark {slip.paymentStatus === "Paid" ? "Pending" : "Paid"} }
))}
{!slips.length &&
}
);
}
function Charts() {
const weekly = [
{ day: "Mon", present: 42 },
{ day: "Tue", present: 45 },
{ day: "Wed", present: 39 },
{ day: "Thu", present: 48 },
{ day: "Fri", present: 44 },
];
const monthly = [
{ name: "Present", value: 88 },
{ name: "Absent", value: 12 },
];
return (
);
}
function ChartCard({ title, children }) {
return (
);
}
function CompaniesPage() {
const [companies, setCompanies] = useState([]);
const [bosses, setBosses] = useState([]);
const [form, setForm] = useState({ name: "", bossName: "", bossUsername: "", bossPassword: "" });
const [message, setMessage] = useState("");
async function load() {
const [companyRows, bossRows] = await Promise.all([api("/api/companies"), api("/api/company-admins")]);
setCompanies(companyRows);
setBosses(bossRows);
}
useEffect(() => { load().catch((err) => setMessage(err.message)); }, []);
async function save(event) {
event.preventDefault();
await api("/api/companies", { method: "POST", body: form });
setForm({ name: "", bossName: "", bossUsername: "", bossPassword: "" });
setMessage("Company and Boss login created");
await load();
}
async function toggle(company) {
await api(`/api/companies/${company.id || company._id}`, { method: "PUT", body: { status: company.status === "Active" ? "Inactive" : "Active" } });
await load();
}
return (
{companies.map((company) => toggle(company)} className="min-h-11 rounded-2xl bg-slate-100 px-4 font-black">Toggle} />)}
{bosses.map((boss) => )}
);
}
function PlansPage() {
const [plans, setPlans] = useState([]);
const [payments, setPayments] = useState([]);
const [form, setForm] = useState({ name: "", price: "", employeeLimit: "" });
async function load() {
const [planRows, paymentRows] = await Promise.all([api("/api/subscription-plans"), api("/api/payments")]);
setPlans(planRows);
setPayments(paymentRows);
}
useEffect(() => { load().catch(() => null); }, []);
async function save(event) {
event.preventDefault();
await api("/api/subscription-plans", { method: "POST", body: form });
setForm({ name: "", price: "", employeeLimit: "" });
await load();
}
return (
{plans.map((plan) => )}
{payments.map((payment) => )} {!payments.length && }
);
}
function ReportsPage() {
const [attendance, setAttendance] = useState([]);
const [salary, setSalary] = useState([]);
useEffect(() => {
Promise.all([api("/api/reports/attendance"), api("/api/reports/salary")]).then(([a, s]) => { setAttendance(a); setSalary(s); }).catch(() => null);
}, []);
return (
);
}
function MorePanel({ superAdmin, showBranchTools }) {
return superAdmin ? : ;
}
function BranchesPage({ setToast }) {
const [branches, setBranches] = useState([]);
const [form, setForm] = useState({ branchName: "", branchCode: "", address: "", city: "", state: "", pincode: "", contactPerson: "", mobile: "", email: "", latitude: "", longitude: "", geofenceRadius: "100", shiftStartTime: "09:00", shiftEndTime: "18:00", graceMinutes: "10", isMainBranch: false, status: "Active" });
async function load() {
setBranches(await api("/api/branches"));
}
useEffect(() => { load().catch((err) => setToast(err.message)); }, [setToast]);
async function save(event) {
event.preventDefault();
await api("/api/branches", { method: "POST", body: form });
setToast("Branch saved");
setForm({ ...form, branchName: "", branchCode: "" });
await load();
}
async function toggle(branch) {
await api(`/api/branches/${branch.id || branch._id}`, { method: "PUT", body: { status: branch.status === "Active" ? "Inactive" : "Active" } });
await load();
}
return (
{branches.map((branch) => toggle(branch)} className="min-h-11 rounded-2xl bg-slate-100 px-4 font-black">Toggle} />)}
);
}
function AttendanceQrPanel() {
const [branches, setBranches] = useState([]);
const [qrs, setQrs] = useState([]);
const [form, setForm] = useState({ qrLabel: "Office QR", branchId: "" });
const [token, setToken] = useState("");
async function load() {
const [branchRows, qrRows] = await Promise.all([api("/api/branches"), api("/api/attendance-qr")]);
setBranches(branchRows);
setQrs(qrRows);
setForm((old) => ({ ...old, branchId: old.branchId || branchRows[0]?.id || "" }));
}
useEffect(() => { load().catch(() => null); }, []);
async function generate() {
const qr = await api("/api/attendance-qr/generate", { method: "POST", body: form });
setToken(qr.qrToken);
await load();
}
async function regenerate(qr) {
const next = await api("/api/attendance-qr/regenerate", { method: "POST", body: { id: qr.id || qr._id } });
setToken(next.qrToken);
await load();
}
async function toggleStatus(qr) {
await api("/api/attendance-qr/status", { method: "POST", body: { id: qr.id || qr._id, isActive: !qr.isActive } });
await load();
}
return (
setForm({ ...form, qrLabel: value })} />
Branch setForm({ ...form, branchId: event.target.value })}>{branches.map((branch) => {branch.branchName} )}
Generate QR
{token && QR Token {token}
window.print()} className="mt-2 min-h-11 rounded-2xl bg-slate-100 px-4 font-black">Print / Download QR }
{qrs.map((qr) =>
(branch.id || branch._id) === qr.branchId)?.branchName || ""}`} action={ regenerate(qr)} className="min-h-11 rounded-2xl bg-slate-100 px-4 font-black">Regenerate toggleStatus(qr)} className="min-h-11 rounded-2xl bg-slate-100 px-4 font-black">{qr.isActive ? "Set Inactive" : "Set Active"}
} />)}
);
}
function BranchManagersPanel() {
const [branches, setBranches] = useState([]);
const [managers, setManagers] = useState([]);
const [form, setForm] = useState({ name: "", mobile: "", email: "", branchId: "", username: "", password: "", status: "Active" });
async function load() {
const [branchRows, managerRows] = await Promise.all([api("/api/branches"), api("/api/branch-managers")]);
setBranches(branchRows);
setManagers(managerRows);
setForm((old) => ({ ...old, branchId: old.branchId || branchRows[0]?.id || "" }));
}
useEffect(() => { load().catch(() => null); }, []);
async function save(event) {
event.preventDefault();
await api("/api/branch-managers", { method: "POST", body: form });
setForm({ ...form, name: "", mobile: "", username: "", password: "" });
await load();
}
return (
{managers.map((manager) => )}
);
}
function PremiumFeaturesPanel() {
function request(name) {
window.alert(`${name} is a premium feature. Contact Super Admin to enable.`);
}
return (
request("Face Recognition")} className="min-h-11 rounded-2xl bg-primary px-4 font-black text-white">Request Activation} />
request("Biometric Device Integration")} className="min-h-11 rounded-2xl bg-primary px-4 font-black text-white">Request Activation} />
);
}
function Notifications({ notices }) {
const [items, setItems] = useState(notices);
useEffect(() => setItems(notices), [notices]);
async function read(notice) {
const id = notice.id || notice._id;
await api(`/api/notifications/${id}/read`, { method: "PUT" });
setItems((old) => old.map((item) => (item.id || item._id) === id ? { ...item, read: true } : item));
}
return {items.map((notice) =>
{notice.message} {timeAgo(notice.createdAt)} / {notice.read ? "Read" : "Unread"}
{!notice.read && read(notice)} className="mt-3 min-h-11 rounded-2xl bg-primary px-4 font-black text-white">Mark Read })} {!items.length &&
}
;
}
function SelfieCapture({ onCapture, onClose }) {
const videoRef = React.useRef(null);
const streamRef = React.useRef(null);
const [error, setError] = useState("");
useEffect(() => {
navigator.mediaDevices?.getUserMedia({ video: true })
.then((stream) => {
streamRef.current = stream;
if (videoRef.current) videoRef.current.srcObject = stream;
})
.catch(() => setError("Camera permission is required for selfie attendance."));
return () => streamRef.current?.getTracks().forEach((track) => track.stop());
}, []);
function capture() {
const video = videoRef.current;
if (!video) return;
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth || 640;
canvas.height = video.videoHeight || 480;
canvas.getContext("2d").drawImage(video, 0, 0, canvas.width, canvas.height);
onCapture(canvas.toDataURL("image/jpeg", 0.82));
}
return (
Selfie Attendance
{error ? {error}
: }
Capture
Cancel
);
}
function QrScanner({ onScan, onClose }) {
const videoRef = React.useRef(null);
const streamRef = React.useRef(null);
const frameRef = React.useRef(null);
const detectorRef = React.useRef(null);
const [error, setError] = useState("");
useEffect(() => {
let stopped = false;
async function start() {
if (!("BarcodeDetector" in window)) {
setError("QR camera scanning is not supported in this browser. Enter the QR token manually.");
return;
}
detectorRef.current = new window.BarcodeDetector({ formats: ["qr_code"] });
try {
const stream = await navigator.mediaDevices?.getUserMedia({ video: { facingMode: "environment" } });
if (!stream) throw new Error("Camera permission is required to scan QR.");
streamRef.current = stream;
if (videoRef.current) {
videoRef.current.srcObject = stream;
await videoRef.current.play();
}
const scan = async () => {
if (stopped || !videoRef.current || !detectorRef.current) return;
try {
const codes = await detectorRef.current.detect(videoRef.current);
const token = codes?.[0]?.rawValue;
if (token) {
onScan(token);
return;
}
} catch {
setError("Unable to read QR from camera. Try again or enter the token manually.");
}
frameRef.current = requestAnimationFrame(scan);
};
frameRef.current = requestAnimationFrame(scan);
} catch (err) {
setError(err.message || "Camera permission is required to scan QR.");
}
}
start();
return () => {
stopped = true;
if (frameRef.current) cancelAnimationFrame(frameRef.current);
streamRef.current?.getTracks().forEach((track) => track.stop());
};
}, [onScan]);
return (
Scan Office QR
{error ? {error}
: }
Cancel
);
}
function NotificationDrawer({ notices, onClose }) {
return (
Notifications
{notices.length} total
);
}
function Profile() {
const [form, setForm] = useState({ currentPassword: "", newPassword: "" });
const [message, setMessage] = useState("");
async function save(event) {
event.preventDefault();
await api("/api/profile/change-password", { method: "PUT", body: form });
setForm({ currentPassword: "", newPassword: "" });
setMessage("Password changed");
}
return (
);
}
function AdminTools() {
const [health, setHealth] = useState(null);
const [backups, setBackups] = useState([]);
const [message, setMessage] = useState("");
const [secret, setSecret] = useState("demo-secret");
async function load() {
const [healthRows, backupRows] = await Promise.all([api("/api/system/health"), api("/api/backups")]);
setHealth(healthRows);
setBackups(backupRows);
}
useEffect(() => { load().catch((err) => setMessage(err.message)); }, []);
async function backup() {
await api("/api/backups/manual", { method: "POST" });
setMessage("Manual backup created");
await load();
}
async function restore(id) {
await api("/api/backups/restore", { method: "POST", body: { backupId: id, setupSecret: secret, confirmation: "RESTORE DATA" } });
setMessage("Backup restored");
await load();
}
async function freshReset() {
await api("/api/system/fresh-reset", { method: "POST", body: { setupSecret: secret, confirmation: "DELETE ALL DATA" } });
setMessage("Fresh Reset complete. Login again if session was cleared.");
}
return (
{health?.checks?.map((check) => ) || }
);
}
function CompanySettingsPanel() {
const [settings, setSettings] = useState(null);
const [message, setMessage] = useState("");
useEffect(() => { api("/api/company/settings").then(setSettings).catch((err) => setMessage(err.message)); }, []);
if (!settings) return ;
async function save(event) {
event.preventDefault();
const saved = await api("/api/company/settings", { method: "PUT", body: settings });
setSettings(saved);
setMessage("Settings saved");
}
return (
);
}
function Panel({ title, children }) {
return ;
}
function RowCard({ title, meta, action }) {
return ;
}
function Input({ label, value, onChange, type = "text", required = false }) {
return {label} onChange(event.target.value)} className="min-h-12 w-full rounded-full border border-line px-4 outline-none focus:ring-4 focus:ring-primary/20" /> ;
}
function ReportCard({ title, rows, kinds, fileName }) {
return (
{kinds.map((kind) => )}
exportCsv(fileName, rows)} className="min-h-11 rounded-2xl bg-primary px-5 font-black text-white">Export CSV
exportCsv(fileName.replace(".csv", ".xls"), rows)} className="min-h-11 rounded-2xl bg-slate-100 px-5 font-black">Export Excel
window.print()} className="min-h-11 rounded-2xl bg-slate-100 px-5 font-black">Export PDF
);
}
function exportCsv(fileName, rows) {
const safeRows = rows.length ? rows : [{ message: "No records" }];
const headers = Object.keys(safeRows[0]);
const csv = [headers.join(","), ...safeRows.map((row) => headers.map((key) => `"${String(row[key] ?? "").replaceAll('"', '""')}"`).join(","))].join("\n");
const url = URL.createObjectURL(new Blob([csv], { type: "text/csv" }));
const link = document.createElement("a");
link.href = url;
link.download = fileName;
link.click();
URL.revokeObjectURL(url);
}
async function downloadSalaryPdf(slip) {
try {
const id = slip.id || slip._id;
const token = sessionStorage.getItem("authToken");
const response = await fetch(`${API_BASE}/api/employee-salary/slips/${id}/download`, {
headers: token ? { Authorization: `Bearer ${token}` } : {},
});
if (!response.ok) {
const text = await response.text();
throw new Error(text || "Unable to download PDF");
}
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `${slip.slipNumber || "salary-slip"}.pdf`;
link.click();
URL.revokeObjectURL(url);
} catch (error) {
window.alert(error.message || "Unable to download PDF");
}
}
function HeroGreeting({ title, subtitle }) {
return ;
}
function Info({ label, value }) {
return ;
}
function StatusBadge({ status }) {
const color = status === "Present" || status === "Paid" ? "green" : status === "Late" || status === "Pending" ? "orange" : status === "Absent" ? "red" : "blue";
return {status} ;
}
function Toast({ message, onClose }) {
useEffect(() => {
const id = setTimeout(onClose, 3200);
return () => clearTimeout(id);
}, [onClose]);
return {message} ;
}
function EmptyState({ text }) {
return {text}
;
}
function safeJson(value) {
try {
return value ? JSON.parse(value) : null;
} catch {
return null;
}
}
function time(value) {
return value ? new Date(value).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "-";
}
function timeAgo(value) {
if (!value) return "";
const minutes = Math.max(1, Math.round((Date.now() - new Date(value).getTime()) / 60000));
return `${minutes} min ago`;
}
function initials(name) {
return String(name || "Employee").split(" ").map((item) => item[0]).join("").slice(0, 2).toUpperCase();
}
function getLocation() {
return new Promise((resolve, reject) => {
if (!navigator.geolocation) return reject(new Error("GPS Required"));
navigator.geolocation.getCurrentPosition(
(position) => resolve({ lat: position.coords.latitude, lng: position.coords.longitude }),
() => reject(new Error("GPS permission is required."))
);
});
}
createRoot(document.getElementById("root")).render( );