update hired page

This commit is contained in:
2026-08-11 19:17:40 +05:30
parent 8a256a4de7
commit 25c4ee4a6a
23 changed files with 2921 additions and 380 deletions

View File

@@ -0,0 +1,93 @@
import React from 'react';
import { ScatterChart } from '@mui/x-charts';
import { cn } from '@/lib/utils';
import { chartSx, TONES, tooltipSx } from './muiChartTheme';
/**
* Department performance — a dot plot, not a bar race.
*
* The bars this replaces encoded hire *count*, which on a dataset where every
* department has hired once drew three identical full-width rectangles. Encoding
* average score instead does not fix it either: 92, 94 and 97 against a
* zero-baseline axis are three more identical bars.
*
* So the scores are plotted as points on a focused axis. The domain starts below
* the lowest score rather than at zero — legitimate here because the axis ticks
* are drawn and read, and because the question is "which department hires
* better", which is a comparison between 90s, not a comparison against nothing.
*
* Volume is stated in the list underneath: one hire per department is a fact,
* not a shape.
*
* `items: [{ name, count, avgScore }]` — supplied by the page.
*/
export function DepartmentPerformance({ items = [], className }) {
if (!items.length) return null;
const scored = items.filter((d) => d.avgScore > 0);
/* Highest at the top: MUI plots ascending y upward, so rank ascending. */
const plotted = [...scored].sort((a, b) => (a.avgScore || 0) - (b.avgScore || 0));
const lowest = Math.min(...plotted.map((d) => d.avgScore));
const highest = Math.max(...plotted.map((d) => d.avgScore));
/* A little air either side, snapped to 5s, so the ticks stay round numbers. */
const min = Math.max(0, Math.floor((lowest - 6) / 5) * 5);
const max = Math.min(100, Math.ceil((highest + 3) / 5) * 5);
return (
<div className={cn('overflow-hidden rounded-xl border border-border bg-surface', className)}>
{plotted.length > 0 ? (
<div className="px-2 pt-4 sm:px-3">
<ScatterChart
height={Math.max(132, plotted.length * 40 + 52)}
margin={{ top: 8, right: 24, bottom: 28, left: 8 }}
xAxis={[{
min,
max,
tickNumber: 4,
valueFormatter: (v) => `${v}`,
}]}
yAxis={[{
min: -0.6,
max: plotted.length - 0.4,
tickInterval: plotted.map((_, i) => i),
valueFormatter: (v) => plotted[Math.round(v)]?.name ?? '',
width: 96,
}]}
series={[{
label: 'Average score',
data: plotted.map((d, i) => ({ x: d.avgScore, y: i, id: d.name })),
markerSize: 7,
valueFormatter: (point) => {
const d = plotted[Math.round(point.y)];
return `${d.name} · avg ${d.avgScore} · ${d.count} ${d.count === 1 ? 'hire' : 'hires'}`;
},
}]}
colors={[TONES.brand]}
grid={{ vertical: true }}
skipAnimation
sx={{ ...chartSx, ...tooltipSx }}
/>
</div>
) : (
<p className="px-4 py-6 text-center text-body-sm text-ink-3">
No scores recorded against these hires yet.
</p>
)}
<ul className="divide-y divide-border border-t border-border">
{[...items].sort((a, b) => (b.avgScore || 0) - (a.avgScore || 0)).map((d) => (
<li key={d.name} className="flex items-baseline justify-between gap-3 px-4 py-2">
<span className="truncate text-body-sm text-ink-2">{d.name}</span>
<span className="flex shrink-0 items-baseline gap-4 text-caption tabular-nums text-ink-4">
<span>{d.count} {d.count === 1 ? 'hire' : 'hires'}</span>
<span className="w-16 text-right font-semibold text-ink-1">
{d.avgScore ? `avg ${d.avgScore}` : '—'}
</span>
</span>
</li>
))}
</ul>
</div>
);
}