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 (
Interactive pipeline mapping AI score quality & hire velocity across departments
Top Department
{ranked[0]?.name || 'Chef'} ({ranked[0]?.avgScore || 97})
Active Units
{ranked.length} Departments
Overall AI Index
{overallAvgScore} / 100
Top Fill Velocity
{Math.min(...ranked.map((r) => r.avgTimeToHire || 2))}d Avg
Total Hires
{totalHires} staff filled
Avg AI Score
{overallAvgScore} / 100
Quality Tier
95+ Top
Placement
100% Verified
Avg Rating
4.9 / 5.0
| Rank | Department | Roles Included | Volume | Avg AI Score |
|---|---|---|---|---|
| {isLeader ? ( #1 ) : ( `0${index + 1}` )} | {d.name} | {d.roles && d.roles.length ? d.roles.join(', ') : d.name} | {d.count} {d.count === 1 ? 'hire' : 'hires'} | {d.avgScore || '—'} |
Hiring pipeline & outcome analytics
Avg AI Score
{activeDept.avgScore || '—'}
Total Hires
{activeDept.count}
Avg Velocity
{activeDept.avgTimeToHire ? `${activeDept.avgTimeToHire}d` : '3d'}
{h.name}
{h.role || activeDept.name}