import React, { useEffect, useState } from "react"; import { AnimatePresence, motion } from "framer-motion"; import { X, Mail, Clock, CheckCircle2, Loader2, AlertCircle, TrendingUp, Zap } from "lucide-react"; import { format, differenceInDays } from "date-fns"; import { orgDashboardService, type MemberDetailData, type PeriodStats, type MemberProjectStats, } from "../services/orgDashboardService"; const MONTH_NAMES = ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"]; const CHART_COLORS = ["#7C3AED", "#06B6D4", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6", "#3B82F6", "#EC4899"]; function formatMinutes(minutes: number) { const h = Math.floor(minutes / 60); const m = minutes % 60; if (h === 0) return `${m}m`; if (m === 0) return `${h}h`; return `${h}h ${m}m`; } function getPeriodLabel(s: PeriodStats, groupedBy: "DAY" | "MONTH"): string { if (groupedBy === "DAY") { return `${String(s.day).padStart(2, "0")}/${String(s.month).padStart(2, "0")}`; } return `${MONTH_NAMES[s.month - 1]}/${String(s.year).slice(2)}`; } function Avatar({ name, photo }: { name: string; photo?: string | null }) { if (photo) return {name}; const initials = name.split(" ").slice(0, 2).map((n) => n[0]).join("").toUpperCase(); return (
{initials}
); } function BarChart({ data, valueKey, color, groupedBy, }: { data: PeriodStats[]; valueKey: "finishedTasksCount" | "totalMinutesWorked"; color: string; groupedBy: "DAY" | "MONTH"; }) { const values = data.map((d) => d[valueKey] as number); const maxVal = Math.max(...values, 1); const chartW = 220; const barAreaH = 80; const barCount = data.length; const gap = barCount > 15 ? 2 : 4; const barW = barCount > 0 ? Math.max(2, Math.floor((chartW - gap * (barCount - 1)) / barCount)) : 10; const showLabel = (i: number) => { if (groupedBy === "DAY") return barCount <= 10 || i % Math.ceil(barCount / 6) === 0; return true; }; return ( {data.map((d, i) => { const val = d[valueKey] as number; const barH = maxVal > 0 ? Math.max(val > 0 ? 2 : 0, Math.round((val / maxVal) * barAreaH)) : 0; const x = i * (barW + gap); const y = barAreaH - barH; const label = showLabel(i) ? getPeriodLabel(d, groupedBy) : ""; return ( {label && ( {label} )} ); })} ); } function MiniPieChart({ data }: { data: MemberProjectStats[] }) { const total = data.reduce((s, d) => s + d.totalMinutes, 0); if (total === 0 || data.length === 0) { return (
Sem dados
); } const cx = 50, cy = 50, r = 38, innerR = 22; let startAngle = -Math.PI / 2; const slices = data.map((d, i) => { const fraction = d.totalMinutes / total; const angle = fraction * 2 * Math.PI; const endAngle = startAngle + angle; const x1 = cx + r * Math.cos(startAngle), y1 = cy + r * Math.sin(startAngle); const x2 = cx + r * Math.cos(endAngle), y2 = cy + r * Math.sin(endAngle); const ix1 = cx + innerR * Math.cos(endAngle), iy1 = cy + innerR * Math.sin(endAngle); const ix2 = cx + innerR * Math.cos(startAngle), iy2 = cy + innerR * Math.sin(startAngle); const largeArc = angle > Math.PI ? 1 : 0; const path = `M ${x1} ${y1} A ${r} ${r} 0 ${largeArc} 1 ${x2} ${y2} L ${ix1} ${iy1} A ${innerR} ${innerR} 0 ${largeArc} 0 ${ix2} ${iy2} Z`; const result = { path, color: CHART_COLORS[i % CHART_COLORS.length], name: d.projectName, fraction }; startAngle = endAngle; return result; }); return ( {slices.map((s, i) => ( ))} ); } interface MemberDetailModalProps { memberId: number | null; orgId: number; startDate: Date; endDate: Date; onClose: () => void; } export const MemberDetailModal: React.FC = ({ memberId, orgId, startDate, endDate, onClose, }) => { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { if (memberId === null) { setData(null); return; } setIsLoading(true); setError(null); setData(null); const start = format(startDate, "yyyy-MM-dd'T'HH:mm:ss"); const end = format(endDate, "yyyy-MM-dd'T'HH:mm:ss"); orgDashboardService .getMemberStats(orgId, memberId, start, end) .then(setData) .catch(() => setError("Falha ao carregar os dados do membro.")) .finally(() => setIsLoading(false)); }, [memberId, orgId, startDate, endDate]); const totalMinutes = data?.periodStats.reduce((s, p) => s + p.totalMinutesWorked, 0) ?? 0; const totalFinished = data?.periodStats.reduce((s, p) => s + p.finishedTasksCount, 0) ?? 0; const daySpan = Math.max(differenceInDays(endDate, startDate), 1); const avgPerDay = totalMinutes / daySpan; const bestPeriod = data?.periodStats.reduce( (best, p) => (p.totalMinutesWorked > (best?.totalMinutesWorked ?? -1) ? p : best), null as PeriodStats | null, ); const bestLabel = bestPeriod && bestPeriod.totalMinutesWorked > 0 ? getPeriodLabel(bestPeriod, data!.groupedBy) : "—"; return ( {memberId !== null && (
{isLoading ? (

Carregando dados...

) : error ? (

{error}

) : data ? (
{/* Header */}

{data.userName}

{data.userEmail}
{/* 4 stat chips */}
{totalFinished}
Concluídas
{formatMinutes(totalMinutes)}
Trabalhado
{formatMinutes(Math.round(avgPerDay))}
Média/dia
{bestLabel}
Melhor período
{/* Bar charts side by side */}
Tarefas Concluídas {data.groupedBy === "DAY" ? "por Dia" : "por Mês"}
Tempo Trabalhado {data.groupedBy === "DAY" ? "por Dia" : "por Mês"}
{/* Pie chart — hours by project */} {data.projectStats.length > 0 && (
Horas por Projeto
{data.projectStats .slice() .sort((a, b) => b.totalMinutes - a.totalMinutes) .map((p, i) => { const total = data.projectStats.reduce((s, x) => s + x.totalMinutes, 0); const pct = total > 0 ? ((p.totalMinutes / total) * 100).toFixed(0) : "0"; return (
{p.projectName} {formatMinutes(p.totalMinutes)} ({pct}%)
); })}
)}
) : null}
)} ); };