"use client"; import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { User, DollarSign, Calendar, Briefcase, Mail, Phone, MapPin, CalendarDays, Building2, Shield, ChevronRight, AlertTriangle, Check, X, Users, LogOut, } from "lucide-react"; import { fetchEmployeeDetail, fetchEmployees, updateEmployee } from "@/lib/supabase/employees-actions"; import { fetchEmployeeLeaveBalance, fetchLeaveRequests } from "@/lib/supabase/leave-actions"; import { fetchPayslipsForRun, fetchPayrollRuns } from "@/lib/supabase/payroll-actions"; import { getCurrentEmployee, getCurrentUserInfo, getOrgStats, submitResignation, reviewResignation, fetchResignations, getUserPermissions, canAccessPage, removeEmployee, } from "@/lib/supabase/rbac-actions"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import PageSkeleton from "./PageSkeleton"; import { Edit2, Save, X as XIcon } from "lucide-react"; const AVATAR_COLORS = ["bg-teal-500", "bg-blue-500", "bg-purple-500", "bg-orange-500", "bg-pink-500"]; function formatDate(dateStr: string): string { if (!dateStr) return "—"; return new Date(dateStr + "T00:00:00").toLocaleDateString("en-AU", { day: "numeric", month: "long", year: "numeric", }); } export default function PortalPage() { const router = useRouter(); const [loading, setLoading] = useState(true); const [greeting, setGreeting] = useState(""); const [userInfo, setUserInfo] = useState<{ fullName: string; email: string } | null>(null); const [employee, setEmployee] = useState(null); const [orgRole, setOrgRole] = useState("employee"); const [customRoles, setCustomRoles] = useState([]); const [leaveBalance, setLeaveBalance] = useState([]); const [leaveRequests, setLeaveRequests] = useState([]); const [payslips, setPayslips] = useState([]); const [teammates, setTeammates] = useState([]); const [stats, setStats] = useState(null); const [permissions, setPermissions] = useState>(new Set()); const [canViewEmployees, setCanViewEmployees] = useState(false); const [canRequestLeave, setCanRequestLeave] = useState(false); // Editing state const [isEditing, setIsEditing] = useState(false); const [isSaving, setIsSaving] = useState(false); const [editError, setEditError] = useState(null); const [formData, setFormData] = useState({ first_name: "", last_name: "", phone: "", address: "", }); // Resignation state const [showResignModal, setShowResignModal] = useState(false); const [resignReason, setResignReason] = useState(""); const [resignLastDay, setResignLastDay] = useState(""); const [resigning, setResigning] = useState(false); const [resignError, setResignError] = useState(null); // Resignation review const [pendingResignations, setPendingResignations] = useState([]); const [canReviewResignation, setCanReviewResignation] = useState(false); useEffect(() => { const today = new Date(); const hour = today.getHours(); if (hour < 12) setGreeting("Good morning"); else if (hour < 17) setGreeting("Good afternoon"); else setGreeting("Good evening"); }, []); useEffect(() => { let cancelled = false; Promise.all([ getCurrentUserInfo().catch(() => null), fetchEmployees().catch(() => []), getCurrentEmployee().catch(() => null), getUserPermissions().catch(() => ({ orgRole: "employee", permissions: new Set(), hierarchyLevel: 0 })), getOrgStats().catch(() => null), canAccessPage("employees").catch(() => false), canAccessPage("leave").catch(() => true), ]) .then(([user, emps, currEmp, perms, orgStats, canEmp, canLeave]) => { if (cancelled) return; setUserInfo(user); setOrgRole(perms.orgRole); setPermissions(perms.permissions as any); setCanViewEmployees(canEmp); setCanRequestLeave(canLeave); if (currEmp) { setEmployee(currEmp); setFormData({ first_name: currEmp.first_name || "", last_name: currEmp.last_name || "", phone: currEmp.phone || "", address: currEmp.address || "", }); // Parse custom roles const roles = (currEmp.employee_roles || []).map((er: any) => er.role); setCustomRoles(roles); // Fetch leave balance fetchEmployeeLeaveBalance(currEmp.id) .then(setLeaveBalance) .catch(() => {}); // Fetch leave requests fetchLeaveRequests() .then((lr) => setLeaveRequests(lr.slice(0, 5))) .catch(() => {}); // Check if user has pending resignation if (currEmp.resignation_status === "submitted" || currEmp.resignation_status === "accepted") { setCanReviewResignation(true); } // Fetch pending resignations (for admins/managers) fetchResignations() .then((r) => setPendingResignations(r)) .catch(() => {}); } // Payslips fetchPayrollRuns() .then((runs) => { const slips: any[] = []; const fetchSlips = async () => { for (const run of runs.slice(0, 3)) { try { const s = await fetchPayslipsForRun(run.id); slips.push(...s.slice(0, 2).map((p) => ({ ...p, period: `${run.period_start} — ${run.period_end}` }))); } catch {} } if (!cancelled) setPayslips(slips); }; fetchSlips(); }) .catch(() => {}); // Teammates setTeammates(emps.slice(1, 5)); setStats(orgStats); }) .catch(() => {}) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, []); const handleEditToggle = () => { if (isEditing) { // Cancel: reset form data setFormData({ first_name: employee?.first_name || "", last_name: employee?.last_name || "", phone: employee?.phone || "", address: employee?.address || "", }); } setIsEditing(!isEditing); setEditError(null); }; const handleSaveProfile = async () => { if (!employee) return; setIsSaving(true); setEditError(null); try { const result = await updateEmployee(employee.id, { first_name: formData.first_name, last_name: formData.last_name, phone: formData.phone, address: formData.address, }); if (result.success) { setEmployee({ ...employee, ...formData }); setIsEditing(false); } else { setEditError(result.error || "Failed to update profile"); } } catch (e: any) { setEditError(e.message || "An unexpected error occurred"); } finally { setIsSaving(false); } }; const handleResign = useCallback(async () => { setResigning(true); setResignError(null); try { if (!resignReason.trim()) { setResignError("Reason is required"); return; } if (!resignLastDay) { setResignError("Last day is required"); return; } const result = await submitResignation(resignReason, resignLastDay); if (!result.success) { setResignError(result.error || "Failed"); return; } setShowResignModal(false); // Refresh const currEmp = await getCurrentEmployee(); if (currEmp) setEmployee(currEmp); } catch (e: any) { setResignError(e.message); } finally { setResigning(false); } }, [resignReason, resignLastDay]); const handleReviewResignation = useCallback(async (employeeId: string, action: "accept" | "reject") => { const result = await reviewResignation(employeeId, action); if (result.success) { setPendingResignations((prev) => prev.filter((r) => r.id !== employeeId)); if (action === "accept") { // Option to remove employee if (confirm("Employee has accepted resignation. Remove them from the organisation?")) { await removeEmployee(employeeId); } } } }, []); if (loading) return ; const displayName = employee ? `${employee.first_name} ${employee.last_name}` : userInfo?.fullName || "User"; const displayEmail = employee?.email || userInfo?.email || "—"; const initials = displayName.split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2); const colorIdx = employee ? (employee.first_name.charCodeAt(0) + employee.last_name.charCodeAt(0)) % AVATAR_COLORS.length : 0; const isOwnerOrAdmin = orgRole === "owner" || orgRole === "admin"; return (
{/* Greeting */}
{initials}

{greeting}, {displayName}.

{employee?.job_title}{employee?.department_name ? ` · ${employee.department_name}` : ""} {customRoles.length > 0 && ` · ${customRoles.map((r: any) => r.name).join(", ")}`} {orgRole}

{/* Resign button (not for owner) */} {!employee?.resignation_status && orgRole !== "owner" && ( )} {employee?.resignation_status === "submitted" && ( Resignation submitted )} {employee?.resignation_status === "accepted" && ( Resignation accepted )}
{/* Resignation Review (for admins/managers) */} {pendingResignations.length > 0 && (

Pending Resignations

{pendingResignations.map((r) => (

{r.first_name} {r.last_name}

Last day: {formatDate(r.resignation_date)}

{r.resignation_reason &&

"{r.resignation_reason}"

}
))}
)} {/* Full-width: Personal Details */}

My Profile

{employee && (
{!isEditing ? ( ) : (
)}
)}
{editError && (
{editError}
)}
{initials}

{displayName}

{employee?.job_title || (userInfo?.fullName ? "Team Member" : "User")}{employee?.department_name ? ` · ${employee.department_name}` : ""}

{isEditing ? (
setFormData({ ...formData, first_name: e.target.value })} className="h-10 focus:ring-teal-500" />
setFormData({ ...formData, last_name: e.target.value })} className="h-10 focus:ring-teal-500" />
setFormData({ ...formData, phone: e.target.value })} className="h-10 focus:ring-teal-500" placeholder="+61 400 000 000" />
setFormData({ ...formData, address: e.target.value })} className="h-10 focus:ring-teal-500" placeholder="Street, Suburb, State, Postcode" />
) : (
{[ { icon: Mail, label: "Email", value: displayEmail }, { icon: Phone, label: "Phone", value: employee?.phone || "—" }, { icon: MapPin, label: "Address", value: employee?.address || "—" }, { icon: CalendarDays, label: "Start Date", value: formatDate(employee?.start_date) }, { icon: Briefcase, label: "Employment Type", value: employee?.employment_type ? employee.employment_type.replace("_", " ").replace(/\b\w/g, (c: string) => c.toUpperCase()) : (employee ? "—" : "Not set") }, { icon: Calendar, label: "Pay Frequency", value: employee?.pay_frequency ? employee.pay_frequency.charAt(0).toUpperCase() + employee.pay_frequency.slice(1) : (employee ? "—" : "Not set") }, { icon: DollarSign, label: "Compensation", value: employee?.salary ? `$${Number(employee.salary).toLocaleString()}/yr` : employee?.hourly_rate ? `$${Number(employee.hourly_rate)}/hr` : (employee ? "—" : "Not set") }, { icon: Shield, label: "Status", value: employee?.status === "active" ? "Active" : employee?.status || "—" }, { icon: Users, label: "Org Role", value: orgRole }, ].map(({ icon: Icon, label, value }) => (

{label}

{value}

))}
)}
{/* Org Stats (owner/admin only) */} {isOwnerOrAdmin && stats && (
{[ { label: "Total Employees", value: stats.totalEmployees, icon: Users }, { label: "Active", value: stats.activeEmployees, icon: Check }, { label: "Departments", value: stats.departments, icon: Building2 }, { label: "Custom Roles", value: stats.roles, icon: Shield }, { label: "Owners", value: stats.ownerCount, icon: User }, { label: "Admins", value: stats.adminCount, icon: User }, ].map(({ label, value, icon: Icon }) => (

{label}

{value}

))}
)} {/* Half-Half Grid: Payslips + Leave */}
{/* My Payslips */}

My Payslips

{payslips.length > 0 ? (
{payslips.map((ps: any) => (

{ps.period ? new Date(ps.period.split(" — ")[0] + "T00:00:00").toLocaleDateString("en-AU", { month: "short", year: "numeric" }) : "Payslip"}

Gross: ${(ps.gross_pay || 0).toFixed(2)}

${(ps.net_pay || 0).toFixed(2)}

Net pay

))}
) : (

No payslips yet

)}
{/* My Leave */}

My Leave

{canRequestLeave && ( )}
{leaveBalance.length > 0 ? (
{leaveBalance.map((lb) => (

{lb.leave_type_name}

{lb.used_days} used · {lb.approved_days - lb.used_days} pending

{lb.remaining_days} left
))}
) : (

No leave balance tracked

)}
{/* Teammates */} {teammates.length > 0 && (

Your Teammates

{canViewEmployees && ( )}
{teammates.map((tm, i) => (
{tm.first_name[0]}{tm.last_name[0]}

{tm.first_name} {tm.last_name}

{tm.job_title &&

{tm.job_title}

}
))}
)} {/* Resignation Modal */} {showResignModal && (
setShowResignModal(false)}>
e.stopPropagation()}>

Submit Resignation

{resignError &&

{resignError}

}