"use client"; import { useEffect, useRef, useState } from "react"; import { trpc } from "~/lib/trpc/client.js"; import { formatCents } from "~/lib/format.js"; import type { SkillEntry } from "@capakraken/shared"; interface ResourceHoverCardProps { resourceId: string; anchorEl: HTMLElement; onClose: () => void; } export function ResourceHoverCard({ resourceId, anchorEl, onClose }: ResourceHoverCardProps) { const ref = useRef(null); const [pos, setPos] = useState({ left: 0, top: 0 }); const { data, isLoading } = trpc.resource.getHoverCard.useQuery( { id: resourceId }, { staleTime: 60_000 }, ); // Position relative to anchor element useEffect(() => { const rect = anchorEl.getBoundingClientRect(); setPos({ left: rect.right + 8, top: Math.min(rect.top, window.innerHeight - 320), }); }, [anchorEl]); // Close on outside click useEffect(() => { function handleClick(e: MouseEvent) { if (ref.current && !ref.current.contains(e.target as Node) && !anchorEl.contains(e.target as Node)) { onClose(); } } document.addEventListener("mousedown", handleClick); return () => document.removeEventListener("mousedown", handleClick); }, [onClose, anchorEl]); const skills = (data?.skills ?? []) as unknown as SkillEntry[]; const mainSkills = skills.filter((s) => s.isMainSkill); const topSkills = skills .filter((s) => !s.isMainSkill && s.proficiency >= 4) .sort((a, b) => b.proficiency - a.proficiency) .slice(0, 6); const popoverStyle: React.CSSProperties = { position: "fixed", left: Math.min(pos.left, window.innerWidth - 300), top: pos.top, zIndex: 50, width: 280, }; return (
{isLoading || !data ? (
Loading...
) : ( <> {/* Header */}
{data.displayName.slice(0, 2).toUpperCase()}
{data.displayName}
{data.eid}
{/* Role & Chapter */}
{data.areaRole && (
Role
{data.areaRole.color && ( )} {data.areaRole.name}
)} {data.chapter && (
Chapter
{data.chapter}
)} {data.country && (
Location
{data.country.name}
)} {data.managementLevel && (
Level
{data.managementLevel.name}
)}
{/* Rates */}
LCR
{formatCents(data.lcrCents)} {data.currency}/h
UCR
{formatCents(data.ucrCents)} {data.currency}/h
Chg%
{data.chargeabilityTarget}%
{/* Main Skills */} {mainSkills.length > 0 && (
Main Skills
{mainSkills.map((s) => ( {s.skill} ))}
)} {/* Top Skills */} {topSkills.length > 0 && (
Top Skills
{topSkills.map((s) => ( {s.skill} L{s.proficiency} ))}
)} {/* No skills */} {skills.length === 0 && (
No skills imported yet.
)}
)}
); }