Files
krow_talent_app/src/components/charts/DepartmentPerformance.tsx
Aravind d440036211 chore(ts-migration): migrate UI primitives and design system to TypeScript
Phase 6. 57 files: 23 vendored shadcn primitives, 30 design-system components,
4 charts. Plus `src/components/ds/props.ts`, which is types only.

Renaming these alone took typecheck from 37 to 1008, and the reason is worth
recording because it is the shape of every remaining phase.

These components had NO prop contract. No PropTypes, no validation: in the
JavaScript every prop was optional and every extra prop was spread onto the
underlying element. TypeScript infers a destructured parameter WITHOUT a default
as REQUIRED, so the moment the files became `.tsx` it invented a rule the
components never had and rejected several hundred call sites that have always
worked. That is the compiler describing its own inference, not a defect it
found.

Three mechanical fixes, each restoring a contract that already existed:

  - 57 JSDoc `@type {React.ForwardRefExoticComponent<any>}` annotations become
    real TypeScript annotations. Those comments were the previous authors'
    deliberate compatibility types; JSDoc stops applying in a `.tsx` file, so
    converting them preserves an intent that was already written down.

  - 61 `React.forwardRef(...)` calls gain `<any, any>`. Without generics `ref`
    infers `ForwardedRef<unknown>`, which no element's `Ref<T>` accepts - so
    every primitive that forwards a ref to a `div` failed on the ref, not the
    props.

  - 78 component signatures take `DsProps`, a documented alias for
    `Record<string, any>`. It exists so the decision is recorded once and is
    greppable when someone tightens it, rather than being 78 bare `any`s with
    no explanation between them. The prop NAMES are not lost: every component
    still destructures them by name, which is where a reader looks.

Four files needed real types rather than compatibility ones. `ds/toast` takes
react-hot-toast's own `ToastOptions`, which narrows `position` to its
`ToastPosition` union instead of widening to `string` - the widening was what
made all six calls unassignable. `ds/Pagination`'s page range is genuinely
`(number | string)[]`, because it interleaves page numbers with '…' markers that
the renderer tests for. `ds/Field` narrows `children.props` at three reads, and
`ds/Avatar` needed the ref generic.

Two of my own automated passes were wrong and were caught rather than shipped. A
props-interface generator dropped alternating props, because non-overlapping
regex matches consume the separating comma - it made things worse (83 file
errors to 146) and was reverted wholesale. A second pass missed every
multi-line signature whose defaults contain a `)`, such as `onClose = () => {}`;
that needed a brace matcher rather than a character class.

56 of 57 files emit byte-identical JavaScript. The one exception is `ds/toast`,
where a JSDoc type CAST - `/** @type {ToastPosition} */ ('bottom-center')` -
became a real annotation, so the emitted output loses a comment and a pair of
now-redundant parentheses. The value is `"bottom-center"` either way; the
minified outputs differ only in esbuild's choice of mangled local names.

Verified: tsc 37 -> 35, set-difference showing zero introduced and two removed;
zero errors remain in any Phase 6 file; npm test 1684/1691 with the same seven
failures; lint 0 errors; build succeeds with the API origin inlined; baseline
artifacts untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
2026-09-17 23:59:49 +05:30

620 lines
31 KiB
TypeScript

import type { DsProps } from '@/components/ds/props';
import React, { useState, useMemo } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
Trophy,
Sparkles,
Workflow,
Table as TableIcon,
Users,
Clock,
ArrowRight,
X,
ChefHat,
ShieldCheck,
Wine,
Building2,
} from 'lucide-react';
import { cn } from '@/lib/utils';
/**
* Animated Flowchart & Department Performance Component
*
* Renders an interactive, animated flow chart mapping department performance,
* AI scores, and hiring outcomes connected via glowing SVG flow paths.
*/
// Helper to choose iconic representation per department
function getDeptIcon(name = '') {
const lower = name.toLowerCase();
if (lower.includes('chef') || lower.includes('kitchen') || lower.includes('cook')) return ChefHat;
if (lower.includes('security') || lower.includes('guard') || lower.includes('safety')) return ShieldCheck;
if (lower.includes('bar') || lower.includes('drink') || lower.includes('mixologist')) return Wine;
if (lower.includes('front') || lower.includes('desk') || lower.includes('concierge')) return Building2;
if (lower.includes('event') || lower.includes('manager')) return Trophy;
if (lower.includes('housekeeping') || lower.includes('clean')) return Sparkles;
return Building2;
}
// Helper to get color tones
function getDeptColor(index, avgScore) {
if (index === 0 && avgScore >= 90) {
return {
border: 'border-blue-500/40 hover:border-blue-500',
bg: 'bg-gradient-to-br from-blue-50/80 via-white to-blue-50/30 dark:from-blue-950/30 dark:via-gray-900 dark:to-blue-900/10',
badgeBg: 'bg-blue-600 text-white shadow-blue-500/25 shadow-md',
ring: 'text-blue-600',
glow: 'shadow-[0_0_25px_rgba(37,99,235,0.15)]',
text: 'text-blue-600 dark:text-blue-400',
path: '#2563eb',
};
}
if (index === 1) {
return {
border: 'border-indigo-500/30 hover:border-indigo-500/80',
bg: 'bg-gradient-to-br from-indigo-50/50 via-white to-indigo-50/20 dark:from-indigo-950/20 dark:via-gray-900 dark:to-indigo-900/10',
badgeBg: 'bg-indigo-600 text-white',
ring: 'text-indigo-600',
glow: 'shadow-[0_0_20px_rgba(79,70,229,0.1)]',
text: 'text-indigo-600 dark:text-indigo-400',
path: '#4f46e5',
};
}
if (index === 2) {
return {
border: 'border-emerald-500/30 hover:border-emerald-500/80',
bg: 'bg-gradient-to-br from-emerald-50/50 via-white to-emerald-50/20 dark:from-emerald-950/20 dark:via-gray-900 dark:to-emerald-900/10',
badgeBg: 'bg-emerald-600 text-white',
ring: 'text-emerald-600',
glow: 'shadow-[0_0_20px_rgba(16,185,129,0.1)]',
text: 'text-emerald-600 dark:text-emerald-400',
path: '#10b981',
};
}
if (index === 3) {
return {
border: 'border-purple-500/30 hover:border-purple-500/80',
bg: 'bg-gradient-to-br from-purple-50/50 via-white to-purple-50/20 dark:from-purple-950/20 dark:via-gray-900 dark:to-purple-900/10',
badgeBg: 'bg-purple-600 text-white',
ring: 'text-purple-600',
glow: 'shadow-[0_0_20px_rgba(147,51,234,0.1)]',
text: 'text-purple-600 dark:text-purple-400',
path: '#9333ea',
};
}
if (index === 4) {
return {
border: 'border-amber-500/30 hover:border-amber-500/80',
bg: 'bg-gradient-to-br from-amber-50/50 via-white to-amber-50/20 dark:from-amber-950/20 dark:via-gray-900 dark:to-amber-900/10',
badgeBg: 'bg-amber-600 text-white',
ring: 'text-amber-600',
glow: 'shadow-[0_0_20px_rgba(217,119,6,0.1)]',
text: 'text-amber-600 dark:text-amber-400',
path: '#d97706',
};
}
return {
border: 'border-teal-500/30 hover:border-teal-500/80',
bg: 'bg-gradient-to-br from-teal-50/50 via-white to-teal-50/20 dark:from-teal-950/20 dark:via-gray-900 dark:to-teal-900/10',
badgeBg: 'bg-teal-600 text-white',
ring: 'text-teal-600',
glow: 'shadow-[0_0_20px_rgba(20,184,166,0.1)]',
text: 'text-teal-600 dark:text-teal-400',
path: '#14b8a6',
};
}
/** @param {any} props */
export function DepartmentPerformance({ items = [], className = '' }: DsProps) {
const [viewMode, setViewMode] = useState('flowchart'); // 'flowchart' | 'table'
const [activeDept, setActiveDept] = useState(null);
// Rank departments by avgScore descending
const ranked = useMemo(() => {
return [...items].sort(
(a, b) => (b.avgScore || 0) - (a.avgScore || 0) || (b.count || 0) - (a.count || 0)
);
}, [items]);
const totalHires = useMemo(() => items.reduce((acc, curr) => acc + (curr.count || 0), 0), [items]);
const overallAvgScore = useMemo(() => {
const scored = items.filter((i) => i.avgScore > 0);
if (!scored.length) return 0;
return Math.round(scored.reduce((acc, curr) => acc + curr.avgScore, 0) / scored.length);
}, [items]);
if (!ranked.length) return null;
return (
<div className={cn('overflow-hidden rounded-2xl border border-border bg-surface shadow-sm transition-all', className)}>
{/* Header Bar & View Switcher */}
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-border px-4 py-3.5 sm:px-6">
<div className="flex items-center gap-2.5">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-blue-50 text-blue-600 dark:bg-blue-950/50 dark:text-blue-400">
<Workflow className="h-4 w-4" />
</div>
<div>
<h3 className="font-heading text-body font-semibold text-ink-1">
Department Performance & Hiring Flow
</h3>
<p className="text-caption text-ink-3">
Interactive pipeline mapping AI score quality & hire velocity across departments
</p>
</div>
</div>
{/* View Mode Toggle Switch */}
<div className="flex items-center rounded-xl bg-surface-subtle p-1 border border-border/60">
<button
type="button"
onClick={() => setViewMode('flowchart')}
className={cn(
'flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-caption font-medium transition-all',
viewMode === 'flowchart'
? 'bg-surface text-ink-1 shadow-xs font-semibold'
: 'text-ink-3 hover:text-ink-1'
)}
>
<Workflow className="h-3.5 w-3.5 text-blue-600" />
<span>Flowchart</span>
</button>
<button
type="button"
onClick={() => setViewMode('table')}
className={cn(
'flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-caption font-medium transition-all',
viewMode === 'table'
? 'bg-surface text-ink-1 shadow-xs font-semibold'
: 'text-ink-3 hover:text-ink-1'
)}
>
<TableIcon className="h-3.5 w-3.5 text-ink-3" />
<span>Scorecard</span>
</button>
</div>
</div>
{/* Executive Quick Stats Bar for Department Breakdown */}
<div className="grid grid-cols-2 gap-3 border-b border-border bg-surface-subtle/50 px-4 py-3 sm:grid-cols-4 sm:px-6">
<div className="flex items-center gap-2.5">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-amber-100 text-amber-700 dark:bg-amber-950 dark:text-amber-400">
<Trophy className="h-4 w-4" />
</div>
<div>
<p className="text-[10px] uppercase tracking-wider text-ink-4 font-semibold">Top Department</p>
<p className="font-heading text-body-sm font-bold text-ink-1">
{ranked[0]?.name || 'Chef'} <span className="text-blue-600 font-semibold">({ranked[0]?.avgScore || 97})</span>
</p>
</div>
</div>
<div className="flex items-center gap-2.5">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-400">
<Building2 className="h-4 w-4" />
</div>
<div>
<p className="text-[10px] uppercase tracking-wider text-ink-4 font-semibold">Active Units</p>
<p className="font-heading text-body-sm font-bold text-ink-1">
{ranked.length} Departments
</p>
</div>
</div>
<div className="flex items-center gap-2.5">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-400">
<Sparkles className="h-4 w-4" />
</div>
<div>
<p className="text-[10px] uppercase tracking-wider text-ink-4 font-semibold">Overall AI Index</p>
<p className="font-heading text-body-sm font-bold text-emerald-600 dark:text-emerald-400">
{overallAvgScore} / 100
</p>
</div>
</div>
<div className="flex items-center gap-2.5">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-purple-100 text-purple-700 dark:bg-purple-950 dark:text-purple-400">
<Clock className="h-4 w-4" />
</div>
<div>
<p className="text-[10px] uppercase tracking-wider text-ink-4 font-semibold">Top Fill Velocity</p>
<p className="font-heading text-body-sm font-bold text-ink-1">
{Math.min(...ranked.map((r) => r.avgTimeToHire || 2))}d Avg
</p>
</div>
</div>
</div>
{/* Main Content Area */}
<div className="p-3 sm:p-4">
<AnimatePresence mode="wait">
{viewMode === 'flowchart' ? (
<motion.div
key="flowchart-view"
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.2 }}
className="relative"
>
{/* Flowchart Diagram Container — Redesigned into Full-Width Executive Dashboard Layout */}
<div className="relative rounded-2xl border border-border/70 bg-gradient-to-br from-surface via-surface-subtle/30 to-surface p-4 sm:p-5 overflow-hidden shadow-xs space-y-5">
{/* 1. Top Integrated Flow Header: Pipeline Source (Left) & Hiring Outcomes (Right) */}
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
{/* Pipeline Source Node (Talent Engine) */}
<motion.div
whileHover={{ scale: 1.01 }}
className="relative rounded-2xl border border-blue-500/30 bg-gradient-to-r from-blue-50/60 via-surface to-surface dark:from-blue-950/30 dark:via-surface p-4 shadow-xs transition-all hover:border-blue-500"
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-blue-600 to-indigo-600 text-white shadow-md shadow-blue-500/25">
<Users className="h-5 w-5" />
</div>
<div>
<span className="inline-flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-wider text-blue-600 dark:text-blue-400">
<span className="h-2 w-2 rounded-full bg-blue-600 animate-pulse" />
Pipeline Source
</span>
<h4 className="font-heading text-body-lg font-bold text-ink-1">
Talent Engine
</h4>
</div>
</div>
<div className="text-right">
<span className="rounded-full bg-blue-100 dark:bg-blue-950 px-2.5 py-1 text-[10px] font-bold text-blue-700 dark:text-blue-400">
100% AI Verified Engine
</span>
</div>
</div>
<div className="mt-3 grid grid-cols-2 gap-3 border-t border-border/60 pt-3 text-caption">
<div className="rounded-xl bg-surface-subtle p-2.5">
<p className="text-[10px] text-ink-4 font-semibold uppercase tracking-wider">Total Hires</p>
<p className="font-heading text-body-lg font-bold tabular-nums text-ink-1 mt-0.5">
{totalHires} <span className="text-caption font-normal text-ink-3">staff filled</span>
</p>
</div>
<div className="rounded-xl bg-surface-subtle p-2.5">
<p className="text-[10px] text-ink-4 font-semibold uppercase tracking-wider">Avg AI Score</p>
<p className="font-heading text-body-lg font-bold tabular-nums text-blue-600 dark:text-blue-400 mt-0.5">
{overallAvgScore} <span className="text-caption font-normal text-ink-3">/ 100</span>
</p>
</div>
</div>
</motion.div>
{/* Hiring Outcomes Node */}
<motion.div
whileHover={{ scale: 1.01 }}
className="relative rounded-2xl border border-emerald-500/30 bg-gradient-to-r from-emerald-50/60 via-surface to-surface dark:from-emerald-950/30 dark:via-surface p-4 shadow-xs transition-all hover:border-emerald-500"
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-emerald-600 to-teal-600 text-white shadow-md shadow-emerald-500/25">
<Sparkles className="h-5 w-5" />
</div>
<div>
<span className="inline-flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-wider text-emerald-600 dark:text-emerald-400">
Hiring Outcomes
</span>
<h4 className="font-heading text-body-lg font-bold text-ink-1">
Verified Quality Impact
</h4>
</div>
</div>
<div className="text-right">
<span className="rounded-full bg-emerald-100 dark:bg-emerald-950 px-2.5 py-1 text-[10px] font-bold text-emerald-700 dark:text-emerald-400">
100% Active Placement
</span>
</div>
</div>
<div className="mt-3 grid grid-cols-3 gap-2 border-t border-border/60 pt-3 text-caption">
<div className="rounded-xl bg-surface-subtle p-2 text-center">
<p className="text-[10px] text-ink-4 font-semibold uppercase tracking-wider">Quality Tier</p>
<p className="font-semibold text-emerald-600 dark:text-emerald-400 mt-0.5 text-body-sm">
95+ Top
</p>
</div>
<div className="rounded-xl bg-surface-subtle p-2 text-center">
<p className="text-[10px] text-ink-4 font-semibold uppercase tracking-wider">Placement</p>
<p className="font-semibold text-ink-1 mt-0.5 text-body-sm">
100% Verified
</p>
</div>
<div className="rounded-xl bg-surface-subtle p-2 text-center">
<p className="text-[10px] text-ink-4 font-semibold uppercase tracking-wider">Avg Rating</p>
<p className="font-semibold text-amber-600 dark:text-amber-400 mt-0.5 text-body-sm">
4.9 / 5.0
</p>
</div>
</div>
</motion.div>
</div>
{/* 2. Department Breakdown Grid (2-Column Grid eliminating empty vertical side spaces) */}
<div>
<div className="flex items-center justify-between mb-3">
<h4 className="font-heading text-body-sm font-bold text-ink-1 uppercase tracking-wider text-[11px]">
Department Performance Units ({ranked.length})
</h4>
<span className="text-[10px] text-ink-4">Click any department card for detailed staff roster</span>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
{ranked.map((dept, index) => {
const colors = getDeptColor(index, dept.avgScore);
const IconComponent = getDeptIcon(dept.name);
const isLeader = index === 0;
return (
<motion.div
key={dept.name}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.04 }}
onClick={() => setActiveDept(dept)}
whileHover={{ scale: 1.015, y: -2 }}
className={cn(
'group relative cursor-pointer rounded-2xl border p-4 backdrop-blur-sm transition-all duration-200 shadow-xs hover:shadow-md',
colors.bg,
colors.border,
colors.glow
)}
>
<div className="flex items-center justify-between gap-3">
{/* Left: Rank, Icon & Department Title */}
<div className="flex items-center gap-3 min-w-0">
<div className="relative flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-surface border border-border shadow-xs">
<IconComponent className={cn('h-5.5 w-5.5', colors.text)} />
{isLeader && (
<div className="absolute -top-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-amber-400 text-amber-950 shadow-xs">
<Trophy className="h-3 w-3" />
</div>
)}
</div>
<div className="min-w-0">
<div className="flex items-center gap-2">
<span className="text-[11px] font-bold tabular-nums text-ink-4">
#{index + 1}
</span>
<h5 className="truncate font-heading text-body font-bold text-ink-1">
{dept.name}
</h5>
{isLeader && (
<span className="rounded-full bg-amber-100 dark:bg-amber-950/60 px-2 py-0.5 text-[9px] font-bold text-amber-700 dark:text-amber-400">
#1 Leader
</span>
)}
</div>
<div className="mt-1 flex items-center gap-2 text-caption text-ink-3">
<span className="font-semibold text-ink-2">{dept.count} {dept.count === 1 ? 'hire' : 'hires'}</span>
{dept.avgTimeToHire ? (
<>
<span>•</span>
<span className="flex items-center gap-0.5 text-emerald-600 dark:text-emerald-400 font-medium">
<Clock className="h-3 w-3" />
{dept.avgTimeToHire}d fill velocity
</span>
</>
) : null}
</div>
</div>
</div>
{/* Right: Score Ring & Navigation Arrow */}
<div className="flex items-center gap-3 shrink-0">
<div className="relative flex items-center justify-center">
<svg className="h-11 w-11 -rotate-90">
<circle
cx="22"
cy="22"
r="17"
fill="transparent"
stroke="currentColor"
strokeWidth="3"
className="text-border"
/>
<circle
cx="22"
cy="22"
r="17"
fill="transparent"
stroke="currentColor"
strokeWidth="3.5"
strokeDasharray={106}
strokeDashoffset={106 - (106 * (dept.avgScore || 0)) / 100}
strokeLinecap="round"
className={cn('transition-all duration-700 ease-out', colors.ring)}
/>
</svg>
<span className="absolute font-heading text-body-sm font-bold tabular-nums text-ink-1">
{dept.avgScore || '—'}
</span>
</div>
<ArrowRight className="h-4 w-4 text-ink-4 group-hover:text-blue-600 transition-colors group-hover:translate-x-1 duration-200" />
</div>
</div>
</motion.div>
);
})}
</div>
</div>
</div>
</motion.div>
) : (
/* Scorecard Table View */
<motion.div
key="table-view"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
transition={{ duration: 0.25 }}
className="overflow-hidden rounded-xl border border-border bg-surface"
>
<div className="overflow-x-auto">
<table className="w-full min-w-[40rem] text-left text-body-sm">
<thead>
<tr className="border-b border-border bg-surface-subtle text-[10px] font-semibold uppercase tracking-wider text-ink-4">
<th className="px-4 py-3">Rank</th>
<th className="px-4 py-3">Department</th>
<th className="px-4 py-3">Roles Included</th>
<th className="px-4 py-3 text-right">Volume</th>
<th className="px-4 py-3 text-right">Avg AI Score</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{ranked.map((d, index) => {
const isLeader = index === 0;
return (
<tr
key={d.name}
onClick={() => setActiveDept(d)}
className="cursor-pointer transition-colors hover:bg-surface-subtle"
>
<td className="px-4 py-3 font-semibold tabular-nums text-ink-4">
{isLeader ? (
<span className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-amber-100 text-amber-700 dark:bg-amber-950 dark:text-amber-400 text-caption font-bold">
#1
</span>
) : (
`0${index + 1}`
)}
</td>
<td className="px-4 py-3 font-heading font-semibold text-ink-1">
{d.name}
</td>
<td className="px-4 py-3 text-caption text-ink-3">
{d.roles && d.roles.length ? d.roles.join(', ') : d.name}
</td>
<td className="px-4 py-3 text-right tabular-nums text-ink-2">
{d.count} {d.count === 1 ? 'hire' : 'hires'}
</td>
<td className="px-4 py-3 text-right font-heading font-bold tabular-nums">
<span
className={cn(
'inline-block rounded-md px-2 py-0.5 text-body-sm font-bold',
isLeader
? 'bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-400'
: 'text-ink-1'
)}
>
{d.avgScore || '—'}
</span>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
{/* Interactive Detail Modal / Drawer on Node Click */}
<AnimatePresence>
{activeDept && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-xs p-4">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
className="relative w-full max-w-lg rounded-2xl border border-border bg-surface p-6 shadow-2xl space-y-5"
>
{/* Modal Header */}
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400">
{React.createElement(getDeptIcon(activeDept.name), { className: 'h-6 w-6' })}
</div>
<div>
<h4 className="font-heading text-title font-bold text-ink-1">
{activeDept.name} Department
</h4>
<p className="text-caption text-ink-3">Hiring pipeline & outcome analytics</p>
</div>
</div>
<button
type="button"
onClick={() => setActiveDept(null)}
className="rounded-lg p-1.5 text-ink-4 hover:bg-surface-subtle hover:text-ink-1 transition-colors"
>
<X className="h-5 w-5" />
</button>
</div>
{/* Quick Key Specs */}
<div className="grid grid-cols-3 gap-3 rounded-xl bg-surface-subtle p-3 text-center border border-border/50">
<div>
<p className="text-caption text-ink-4">Avg AI Score</p>
<p className="font-heading text-title font-bold text-blue-600 dark:text-blue-400">
{activeDept.avgScore || '—'}
</p>
</div>
<div>
<p className="text-caption text-ink-4">Total Hires</p>
<p className="font-heading text-title font-bold text-ink-1">
{activeDept.count}
</p>
</div>
<div>
<p className="text-caption text-ink-4">Avg Velocity</p>
<p className="font-heading text-title font-bold text-emerald-600 dark:text-emerald-400">
{activeDept.avgTimeToHire ? `${activeDept.avgTimeToHire}d` : '3d'}
</p>
</div>
</div>
{/* Hired Personnel Roster */}
{activeDept.hiresList && activeDept.hiresList.length > 0 && (
<div className="space-y-2">
<h5 className="text-caption font-semibold uppercase tracking-wider text-ink-4">
Hired Staff Members
</h5>
<div className="divide-y divide-border rounded-xl border border-border bg-surface">
{activeDept.hiresList.map((h, i) => (
<div key={h.id || i} className="flex items-center justify-between p-3">
<div className="flex items-center gap-2.5">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-100 text-blue-700 font-bold text-caption dark:bg-blue-950 dark:text-blue-400">
{h.name ? h.name[0] : 'S'}
</div>
<div>
<p className="font-medium text-body-sm text-ink-1">{h.name}</p>
<p className="text-caption text-ink-3">{h.role || activeDept.name}</p>
</div>
</div>
<div className="text-right">
<span className="font-semibold text-body-sm text-blue-600 dark:text-blue-400">
{h.score || h.ai_score || activeDept.avgScore}
</span>
</div>
</div>
))}
</div>
</div>
)}
{/* Modal Footer */}
<div className="flex justify-end pt-2">
<button
type="button"
onClick={() => setActiveDept(null)}
className="rounded-xl bg-blue-600 px-4 py-2 text-caption font-semibold text-white shadow-md shadow-blue-500/20 hover:bg-blue-700 transition-colors"
>
Close Inspection
</button>
</div>
</motion.div>
</div>
)}
</AnimatePresence>
</div>
);
}