"use client"; import { useState, useEffect, useCallback } from "react"; import { Plus, X, ChevronDown, ChevronUp, Trash2, Users, CalendarDays, FileText, Star, ArrowLeft, Check, AlertCircle } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Select } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { fetchAppraisalCycles, createAppraisalCycle, fetchAppraisalTemplates, createAppraisalTemplate, deleteAppraisalTemplate, fetchCycleAppraisals, updateAppraisalCycleStatus, } from "@/lib/supabase/appraisal-actions"; import PageSkeleton from "./PageSkeleton"; // ── Star Rating Component ────────────────────────────────────── function StarRating({ value, onChange, readonly = false, size = "sm", }: { value: number; onChange?: (v: number) => void; readonly?: boolean; size?: "sm" | "md" | "lg"; }) { const [hover, setHover] = useState(0); const sizeClass = size === "lg" ? "w-7 h-7" : size === "md" ? "w-5 h-5" : "w-4 h-4"; return (
{[1, 2, 3, 4, 5].map((s) => ( ))}
); } // ── Status Badge Helpers ─────────────────────────────────────── const CYCLE_STATUS_LABELS: Record = { draft: "Draft", active: "Active", completed: "Completed", }; const CYCLE_STATUS_COLORS: Record = { draft: "bg-gray-100 text-gray-600 border-gray-200", active: "bg-teal-100 text-teal-700 border-teal-200", completed: "bg-green-100 text-green-700 border-green-200", }; // ── Template Builder Modal ───────────────────────────────────── function TemplateBuilderModal({ onClose, onSuccess, }: { onClose: () => void; onSuccess: () => void; }) { const [name, setName] = useState(""); const [questions, setQuestions] = useState< { id: string; question: string; type: string }[] >([]); const [saving, setSaving] = useState(false); const [error, setError] = useState(""); const addQuestion = () => { setQuestions((prev) => [ ...prev, { id: crypto.randomUUID(), question: "", type: "rating" }, ]); }; const updateQuestion = (id: string, field: string, value: string) => { setQuestions((prev) => prev.map((q) => (q.id === id ? { ...q, [field]: value } : q)) ); }; const removeQuestion = (id: string) => { setQuestions((prev) => prev.filter((q) => q.id !== id)); }; const moveQuestion = (index: number, direction: "up" | "down") => { setQuestions((prev) => { const arr = [...prev]; const target = direction === "up" ? index - 1 : index + 1; if (target < 0 || target >= arr.length) return prev; [arr[index], arr[target]] = [arr[target], arr[index]]; return arr; }); }; const handleSave = async () => { if (!name.trim()) { setError("Template name is required"); return; } if (questions.length === 0) { setError("Add at least one question"); return; } if (questions.some((q) => !q.question.trim())) { setError("All questions must have text"); return; } setSaving(true); setError(""); const result = await createAppraisalTemplate( name.trim(), questions.map((q) => ({ ...q, question: q.question.trim() })) ); setSaving(false); if (result.success) { onSuccess(); } else { setError(result.error || "Failed to create template"); } }; return (
e.stopPropagation()} > {/* Header */}

New Appraisal Template

Define questions for this appraisal template

{/* Body */}
setName(e.target.value)} placeholder="e.g. Annual Performance Review 2026" className="mt-1" />
{/* Questions */}
{questions.length === 0 && (

No questions yet

Click "Add Question" to start building your template

)}
{questions.map((q, i) => (
{i + 1} updateQuestion(q.id, "question", e.target.value) } placeholder="Enter question text" className="flex-1" />
))}
{error && (
{error}
)}
{/* Footer */}
); } // ── New Cycle Modal ──────────────────────────────────────────── function NewCycleModal({ templates, onClose, onSuccess, }: { templates: { id: string; name: string }[]; onClose: () => void; onSuccess: () => void; }) { const [name, setName] = useState(""); const [templateId, setTemplateId] = useState(""); const [periodStart, setPeriodStart] = useState(""); const [periodEnd, setPeriodEnd] = useState(""); const [saving, setSaving] = useState(false); const [error, setError] = useState(""); const handleSave = async () => { if (!name.trim()) { setError("Cycle name is required"); return; } if (!periodStart || !periodEnd) { setError("Period dates are required"); return; } if (periodEnd <= periodStart) { setError("End date must be after start date"); return; } setSaving(true); setError(""); const result = await createAppraisalCycle({ name: name.trim(), template_id: templateId || undefined, period_start: periodStart, period_end: periodEnd, }); setSaving(false); if (result.success) { onSuccess(); } else { setError(result.error || "Failed to create cycle"); } }; return (
e.stopPropagation()} >

New Appraisal Cycle

Create a review period and auto-generate appraisals

setName(e.target.value)} placeholder="e.g. Q1 2026 Reviews" className="mt-1" />
setPeriodStart(e.target.value)} className="mt-1" />
setPeriodEnd(e.target.value)} className="mt-1" />
{error && (
{error}
)}
); } // ── Cycle Detail View ────────────────────────────────────────── function CycleDetailView({ cycle, onBack, }: { cycle: { id: string; name: string; status: string; period_start: string; period_end: string }; onBack: () => void; }) { const [appraisals, setAppraisals] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { let cancelled = false; fetchCycleAppraisals(cycle.id) .then((a) => { if (!cancelled) setAppraisals(a); }) .catch(() => {}) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [cycle.id]); const handleActivate = async () => { const result = await updateAppraisalCycleStatus(cycle.id, "active"); if (result.success) window.location.reload(); }; const handleComplete = async () => { const result = await updateAppraisalCycleStatus(cycle.id, "completed"); if (result.success) window.location.reload(); }; if (loading) return ; const totalAppraisals = appraisals.length; const submittedCount = appraisals.filter( (a) => a.status === "submitted" ).length; const completionPct = totalAppraisals > 0 ? Math.round((submittedCount / totalAppraisals) * 100) : 0; return (
{/* Back button + header */}

{cycle.name}

{CYCLE_STATUS_LABELS[cycle.status] || cycle.status}

{new Date(cycle.period_start + "T00:00:00").toLocaleDateString( "en-AU", { month: "long", day: "numeric", year: "numeric" } )}{" "} —{" "} {new Date(cycle.period_end + "T00:00:00").toLocaleDateString( "en-AU", { month: "long", day: "numeric", year: "numeric" } )}

{cycle.status === "draft" && ( )} {cycle.status === "active" && ( )}
{/* Progress */}
{submittedCount} of {totalAppraisals} appraisals submitted {completionPct}%
{/* Appraisals Table */} {appraisals.length === 0 ? (

No appraisals in this cycle

) : (
{appraisals.map((a) => ( ))}
Employee Type Reviewer Rating Status

{a.employee ? `${a.employee.first_name} ${a.employee.last_name}` : "Unknown"}

{a.type === "self" ? "Self" : a.type === "manager" ? "Manager" : a.type || "Peer"} {a.reviewer ? `${a.reviewer.first_name} ${a.reviewer.last_name}` : "—"} {a.overall_rating ? (
{[1, 2, 3, 4, 5].map((s) => ( ))} {a.overall_rating}/5
) : ( )}
{a.status === "submitted" ? "Submitted" : "Pending"}
)}
); } // ── Main Performance Page ────────────────────────────────────── export default function PerformancePage() { const [activeTab, setActiveTab] = useState<"cycles" | "templates">("cycles"); const [cycles, setCycles] = useState([]); const [templates, setTemplates] = useState([]); const [loading, setLoading] = useState(true); const [showNewCycleModal, setShowNewCycleModal] = useState(false); const [showNewTemplateModal, setShowNewTemplateModal] = useState(false); const [selectedCycle, setSelectedCycle] = useState(null); const loadData = useCallback(async () => { setLoading(true); try { const [c, t] = await Promise.all([ fetchAppraisalCycles(), fetchAppraisalTemplates(), ]); setCycles(c); setTemplates(t); } catch { // ignore } setLoading(false); }, []); useEffect(() => { loadData(); }, [loadData]); if (loading) return ; // ── Cycle Detail View ── if (selectedCycle) { return (
setSelectedCycle(null)} />
); } return (
{/* Tabs */}

360 Appraisals

Manage appraisal cycles and templates

{activeTab === "cycles" && ( )} {activeTab === "templates" && ( )}
{/* ── Cycles Tab ── */} {activeTab === "cycles" && ( <> {cycles.length === 0 ? (

No appraisal cycles yet

Create your first cycle to start 360-degree reviews

) : (
{cycles.map((c) => { const appraisalCount = Array.isArray(c.appraisals) && c.appraisals.length > 0 ? c.appraisals[0]?.count || 0 : 0; return ( setSelectedCycle(c)} > ); })}
Cycle Name Period Appraisals Completion Status

{c.name}

{c.template && (

{c.template.name}

)}
{new Date( c.period_start + "T00:00:00" ).toLocaleDateString("en-AU", { month: "short", year: "numeric", })}{" "} —{" "} {new Date( c.period_end + "T00:00:00" ).toLocaleDateString("en-AU", { month: "short", year: "numeric", })} {appraisalCount}
0 ? Math.min( (appraisalCount / Math.max(appraisalCount, 1)) * 100, 100 ) : 0 }%`, }} />
{CYCLE_STATUS_LABELS[c.status] || c.status}
)} )} {/* ── Templates Tab ── */} {activeTab === "templates" && ( <> {templates.length === 0 ? (

No appraisal templates yet

Create a template with questions to reuse across cycles

) : (
{templates.map((t) => { const questionCount = t.questions ? JSON.parse(t.questions).length : 0; return ( ); })}
Template Name Questions Created

{t.name}

{questionCount} {new Date(t.created_at).toLocaleDateString("en-AU", { month: "short", day: "numeric", year: "numeric", })}
)} )} {/* ── Modals ── */} {showNewCycleModal && ( setShowNewCycleModal(false)} onSuccess={() => { setShowNewCycleModal(false); loadData(); }} /> )} {showNewTemplateModal && ( setShowNewTemplateModal(false)} onSuccess={() => { setShowNewTemplateModal(false); loadData(); }} /> )}
); }