import { useState, useEffect } from "react"; import Layout from "@/components/Layout"; import SEO from "@/components/SEO"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Target, Plus, TrendingUp, CheckCircle, AlertTriangle, Loader2, ChevronDown, ChevronUp, Trash2, } from "lucide-react"; import { useAuth } from "@/contexts/AuthContext"; import { aethexToast } from "@/lib/aethex-toast"; interface KeyResult { id: string; title: string; description?: string; metric_type: string; start_value: number; current_value: number; target_value: number; unit?: string; progress: number; status: string; due_date?: string; } interface OKR { id: string; objective: string; description?: string; status: string; quarter: number; year: number; progress: number; team?: string; owner_type: string; key_results: KeyResult[]; created_at: string; } interface Stats { total: number; active: number; completed: number; avgProgress: number; } const currentYear = new Date().getFullYear(); const currentQuarter = Math.ceil((new Date().getMonth() + 1) / 3); const getStatusColor = (status: string) => { switch (status) { case "completed": return "bg-green-500/20 text-green-300 border-green-500/30"; case "active": return "bg-blue-500/20 text-blue-300 border-blue-500/30"; case "draft": return "bg-slate-500/20 text-slate-300 border-slate-500/30"; case "on_track": return "bg-green-500/20 text-green-300"; case "at_risk": return "bg-amber-500/20 text-amber-300"; case "behind": return "bg-red-500/20 text-red-300"; default: return "bg-slate-500/20 text-slate-300"; } }; export default function StaffOKRs() { const { session } = useAuth(); const [okrs, setOkrs] = useState([]); const [stats, setStats] = useState({ total: 0, active: 0, completed: 0, avgProgress: 0 }); const [loading, setLoading] = useState(true); const [expandedOkr, setExpandedOkr] = useState(null); const [selectedQuarter, setSelectedQuarter] = useState(currentQuarter.toString()); const [selectedYear, setSelectedYear] = useState(currentYear.toString()); // Dialog states const [createOkrDialog, setCreateOkrDialog] = useState(false); const [addKrDialog, setAddKrDialog] = useState(null); const [updateKrDialog, setUpdateKrDialog] = useState(null); // Form states const [newOkr, setNewOkr] = useState({ objective: "", description: "", quarter: currentQuarter, year: currentYear }); const [newKr, setNewKr] = useState({ title: "", description: "", target_value: 100, metric_type: "percentage", unit: "", due_date: "" }); const [krUpdate, setKrUpdate] = useState({ current_value: 0 }); useEffect(() => { if (session?.access_token) { fetchOkrs(); } }, [session?.access_token, selectedQuarter, selectedYear]); const fetchOkrs = async () => { try { const params = new URLSearchParams(); if (selectedQuarter !== "all") params.append("quarter", selectedQuarter); if (selectedYear !== "all") params.append("year", selectedYear); const res = await fetch(`/api/staff/okrs?${params}`, { headers: { Authorization: `Bearer ${session?.access_token}` }, }); const data = await res.json(); if (res.ok) { setOkrs(data.okrs || []); setStats(data.stats || { total: 0, active: 0, completed: 0, avgProgress: 0 }); } } catch (err) { aethexToast.error("Failed to load OKRs"); } finally { setLoading(false); } }; const createOkr = async () => { if (!newOkr.objective) return; try { const res = await fetch("/api/staff/okrs", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${session?.access_token}`, }, body: JSON.stringify({ action: "create_okr", ...newOkr }), }); if (res.ok) { aethexToast.success("OKR created!"); setCreateOkrDialog(false); setNewOkr({ objective: "", description: "", quarter: currentQuarter, year: currentYear }); fetchOkrs(); } } catch (err) { aethexToast.error("Failed to create OKR"); } }; const addKeyResult = async () => { if (!addKrDialog || !newKr.title) return; try { const res = await fetch("/api/staff/okrs", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${session?.access_token}`, }, body: JSON.stringify({ action: "add_key_result", okr_id: addKrDialog, ...newKr }), }); if (res.ok) { aethexToast.success("Key Result added!"); setAddKrDialog(null); setNewKr({ title: "", description: "", target_value: 100, metric_type: "percentage", unit: "", due_date: "" }); fetchOkrs(); } } catch (err) { aethexToast.error("Failed to add Key Result"); } }; const updateKeyResult = async () => { if (!updateKrDialog) return; try { const res = await fetch("/api/staff/okrs", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${session?.access_token}`, }, body: JSON.stringify({ action: "update_key_result", key_result_id: updateKrDialog.id, ...krUpdate }), }); if (res.ok) { aethexToast.success("Progress updated!"); setUpdateKrDialog(null); fetchOkrs(); } } catch (err) { aethexToast.error("Failed to update progress"); } }; const activateOkr = async (okrId: string) => { try { const res = await fetch("/api/staff/okrs", { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${session?.access_token}`, }, body: JSON.stringify({ id: okrId, status: "active" }), }); if (res.ok) { aethexToast.success("OKR activated!"); fetchOkrs(); } } catch (err) { aethexToast.error("Failed to activate OKR"); } }; const deleteOkr = async (okrId: string) => { if (!confirm("Delete this OKR and all its key results?")) return; try { const res = await fetch(`/api/staff/okrs?id=${okrId}&type=okr`, { method: "DELETE", headers: { Authorization: `Bearer ${session?.access_token}` }, }); if (res.ok) { aethexToast.success("OKR deleted"); fetchOkrs(); } } catch (err) { aethexToast.error("Failed to delete OKR"); } }; if (loading) { return (
); } return (
{/* Header */}

OKRs

Objectives and Key Results

{/* Stats */}

Total OKRs

{stats.total}

Active

{stats.active}

Completed

{stats.completed}

Avg Progress

{stats.avgProgress}%

{/* Filters */}
{/* OKRs List */}
{okrs.map((okr) => ( setExpandedOkr(expandedOkr === okr.id ? null : okr.id)} >
{okr.status.replace("_", " ").replace(/\b\w/g, l => l.toUpperCase())} Q{okr.quarter} {okr.year}
{okr.objective} {okr.description && ( {okr.description} )}

{okr.progress}%

{okr.key_results?.length || 0} Key Results

{expandedOkr === okr.id ? ( ) : ( )}
{expandedOkr === okr.id && (

Key Results

{okr.status === "draft" && ( )}
{okr.key_results?.map((kr) => (
{ setUpdateKrDialog(kr); setKrUpdate({ current_value: kr.current_value }); }} >

{kr.title}

{kr.status.replace("_", " ").replace(/\b\w/g, l => l.toUpperCase())}
{kr.current_value} / {kr.target_value} {kr.unit}
{kr.due_date && (

Due: {new Date(kr.due_date).toLocaleDateString()}

)}
))} {(!okr.key_results || okr.key_results.length === 0) && (

No key results yet. Add one to track progress.

)}
)}
))}
{okrs.length === 0 && (

No OKRs found for this period

)}
{/* Create OKR Dialog */} Create New OKR
setNewOkr({ ...newOkr, objective: e.target.value })} className="bg-slate-700 border-slate-600 text-slate-100" />