"use client"; import { useState, useCallback, useEffect } from "react"; import { Search, Plus, ChevronRight, MoreHorizontal, Filter, Link as LinkIcon, Calendar, Users, X, Check, Copy, AlertTriangle, Trash2, Clock, CheckCircle2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select } from "@/components/ui/select"; import { Label } from "@/components/ui/label"; import { fetchEmployees, fetchDepartments, createJoinLink, fetchInvitations, revokeInvitation, type ExpiryType, } from "@/lib/supabase/employees-actions"; import { EMPLOYEE_STATUS_LABELS, EMPLOYMENT_TYPE_LABELS, getInitials, type EmployeeWithRelations, type Department, type EmployeeStatus, } from "@/lib/supabase/employees-types"; import PageSkeleton from "./PageSkeleton"; const STATUS_COLORS: Record = { active: "bg-emerald-100 text-emerald-700 border-emerald-200", on_leave: "bg-amber-100 text-amber-700 border-amber-200", terminated: "bg-red-100 text-red-700 border-red-200", }; const AVATAR_COLORS = [ "bg-teal-500", "bg-blue-500", "bg-purple-500", "bg-orange-500", "bg-pink-500", "bg-indigo-500", ]; function StatusBadge({ status }: { status: EmployeeStatus }) { return ( {EMPLOYEE_STATUS_LABELS[status]} ); } function Avatar({ first, last, index }: { first: string; last: string; index: number }) { return (
{getInitials(first || "?", last || "?")}
); } export default function EmployeesPage({ onEmployeeClick }: { onEmployeeClick?: (id: string) => void }) { const [activeTab, setActiveTab] = useState<"list" | "invites">("list"); const [employees, setEmployees] = useState([]); const [departments, setDepartments] = useState([]); const [invitations, setInvitations] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); const [deptFilter, setDeptFilter] = useState(""); const [statusFilter, setStatusFilter] = useState(""); // Modal states const [showAddModal, setShowAddModal] = useState(false); const [generatingLink, setGeneratingLink] = useState(false); const [generatedLink, setGeneratedLink] = useState(null); const [expiryType, setExpiryType] = useState("1_week"); const [usageLimit, setUsageLimit] = useState(""); const [copied, setCopied] = useState(false); const loadData = useCallback(async () => { try { const [emps, depts, invs] = await Promise.all([ fetchEmployees(), fetchDepartments(), fetchInvitations(), ]); setEmployees(emps); setDepartments(depts); setInvitations(invs); } catch (err) { console.error("Failed to load employees data:", err); } finally { setLoading(false); } }, []); useEffect(() => { loadData(); }, [loadData]); const handleCreateLink = async () => { setGeneratingLink(true); try { const limit = usageLimit ? parseInt(usageLimit) : null; const result = await createJoinLink(expiryType, limit); if (result.success && result.token) { const url = `${window.location.origin}/join/${result.token}`; setGeneratedLink(url); loadData(); // Refresh invitations list } } catch (err) { console.error("Failed to create join link:", err); } finally { setGeneratingLink(false); } }; const handleCopyLink = () => { if (generatedLink) { navigator.clipboard.writeText(generatedLink); setCopied(true); setTimeout(() => setCopied(false), 2000); } }; const handleRevoke = async (id: string) => { if (confirm("Are you sure you want to revoke this invitation link?")) { await revokeInvitation(id); loadData(); } }; const filtered = employees.filter((emp) => { const fullName = `${emp.first_name || ""} ${emp.last_name || ""}`.toLowerCase(); const matchSearch = !search || fullName.includes(search.toLowerCase()) || (emp.email || "").toLowerCase().includes(search.toLowerCase()); const matchDept = !deptFilter || emp.department_id === deptFilter; const matchStatus = !statusFilter || emp.status === statusFilter; return matchSearch && matchDept && matchStatus; }); if (loading) return ; return (
{/* Header */}

Employees

Manage your team and invitations

{/* Tabs */}
{activeTab === "list" ? ( <> {/* Filters */}
setSearch(e.target.value)} placeholder="Search by name or email..." className="pl-10" />
{/* Table */}
{filtered.length === 0 ? (

No employees found

Try adjusting your filters or invite someone

) : (
{filtered.map((emp, i) => ( onEmployeeClick?.(emp.id)} className="hover:bg-teal-50/30 transition-colors cursor-pointer group" > ))}
Name Title Department Role Status

{emp.first_name} {emp.last_name}

{emp.email}

{emp.job_title || "—"} {emp.department?.name || "—"} {(emp as any).role || 'employee'}
)}
) : ( /* Invites Tab */
{invitations.length === 0 ? (

No invitation links yet

Create your first join link to add team members

) : ( invitations.map((inv) => { const isExpired = inv.expires_at && new Date(inv.expires_at) < new Date(); const isRevoked = inv.status === 'revoked'; const usageText = inv.usage_limit ? `${inv.usage_count} / ${inv.usage_limit}` : `${inv.usage_count} joined`; const isLimitReached = inv.usage_limit && inv.usage_count >= inv.usage_limit; return (
{isRevoked ? ( Revoked ) : isExpired ? ( Expired ) : isLimitReached ? ( Full ) : ( Active )}

Join Link Token

{inv.token}

Usage

{usageText}

Expires

{inv.expires_at ? new Date(inv.expires_at).toLocaleDateString() : "Never"}
{!isRevoked && !isExpired && !isLimitReached && ( )}
); }) )}
)} {/* Add Employee Modal */} {showAddModal && (
setShowAddModal(false)}>
e.stopPropagation()}>

Add New Employee

Generate a secure join link for your new teammate

{!generatedLink ? ( <>
setUsageLimit(e.target.value)} className="h-11" min="1" />

Maximum number of people who can join using this link

) : (

Link Generated!

Share this link with your new teammate

Direct Join Link {expiryType.replace('_', ' ')}
{generatedLink}
)}
)}
); }