update ui design for all pages

This commit is contained in:
2026-08-12 18:56:56 +05:30
parent 25c4ee4a6a
commit 3beb66059b
58 changed files with 7892 additions and 1891 deletions

View File

@@ -1,11 +1,10 @@
import React from 'react';
import { Shield, Star, TrendingUp } from 'lucide-react';
import { Gauge, gaugeClasses } from '@mui/x-charts/Gauge';
import { Badge } from '@/components/ui/badge';
import { getScoreBand } from '@/lib/talentHome';
import ReferencesEndorsements from '@/components/krow/ReferencesEndorsements';
const R = 44;
const STROKE = 8;
const CIRC = 2 * Math.PI * R;
const FICO_MIN = 300;
const FICO_MAX = 850;
@@ -29,51 +28,108 @@ function nextTierFromApp(app) {
return 'Beginner';
}
function CareerScoreMini({ application }) {
/**
* The score, its band, and what both are worth — one block, read top to bottom.
*
* The gauge is the anchor rather than an ornament beside the numbers, so it is
* sized to be the first thing seen and carries the score inside it: label, value
* and range stacked in the arc's own empty middle, which is the only place where
* "834" and "300–850" read as one statement instead of two.
*
* Everything to the right is interpretation — where this person sits and where
* they go next — kept at a subordinate weight so the eye lands on the score
* first and travels outward.
*/
const GAUGE_W = 184;
function CareerScorePrimary({ application }) {
const score = careerScoreFromApp(application);
const pct = Math.max(0, Math.min(1, (score - FICO_MIN) / (FICO_MAX - FICO_MIN)));
const dash = pct * CIRC;
const band = getScoreBand(Math.round(application?.ai_score || 0));
return (
<div className="flex items-center gap-5 bg-white/70 rounded-xl border border-[#E5E7EB] px-4 py-3">
<div className="flex flex-col items-center shrink-0">
<div className="relative" style={{ width: R * 2 + STROKE, height: R * 2 + STROKE }}>
<svg width={R * 2 + STROKE} height={R * 2 + STROKE} className="-rotate-90">
<circle cx={R + STROKE / 2} cy={R + STROKE / 2} r={R} fill="none" stroke="#EFF6FF" strokeWidth={STROKE} />
<circle
cx={R + STROKE / 2}
cy={R + STROKE / 2}
r={R}
fill="none"
stroke="#0A39DF"
strokeWidth={STROKE}
strokeLinecap="round"
strokeDasharray={`${dash} ${CIRC}`}
/>
</svg>
<div className="absolute inset-0 flex flex-col items-center justify-center">
<span className="text-[8px] uppercase tracking-wide text-[#94A3B8] font-medium">Career Score</span>
<span className="text-[22px] font-bold text-[#0A39DF] leading-none">{score}</span>
<span className="text-[8px] text-[#94A3B8] font-medium">300-850</span>
<div className="grid gap-x-7 gap-y-5 sm:grid-cols-[auto_minmax(0,1fr)] sm:items-center">
{/* Layer 1 — the primary signal */}
<div className="flex flex-col items-center gap-2.5">
<div
className="relative shrink-0"
style={{ width: GAUGE_W, height: Math.round(GAUGE_W * 0.577) }}
>
<Gauge
value={score}
valueMin={FICO_MIN}
valueMax={FICO_MAX}
startAngle={-90}
endAngle={90}
innerRadius="89%"
outerRadius="100%"
cornerRadius="50%"
text={() => null}
skipAnimation
aria-hidden="true"
sx={{
width: GAUGE_W,
height: GAUGE_W,
marginTop: `-${Math.round(GAUGE_W * 0.215)}px`,
/* Brand blue for the score it earned, a hairline-weight neutral
for the rest of the range — the same pairing the rest of the
product uses for "achieved against available". */
[`& .${gaugeClasses.valueArc}`]: { fill: '#0A39DF' },
[`& .${gaugeClasses.referenceArc}`]: { fill: 'hsl(var(--border))' },
}}
/>
{/* The arc's middle is empty by construction; the score belongs in it.
Stacked with room between each line so the label never crowds the
number it is labelling. */}
<div
className="pointer-events-none absolute inset-x-0 bottom-0 flex flex-col items-center justify-end pb-1"
role="img"
aria-label={`Career score ${score} of a ${FICO_MIN} to ${FICO_MAX} range. Level: ${band.label}.`}
>
<span className="text-[9px] font-semibold uppercase leading-none tracking-[0.08em] text-ink-4">
Career score
</span>
<span className="mt-1.5 font-heading text-[32px] font-bold leading-none tabular-nums text-krow-blue">
{score}
</span>
<span className="mt-1.5 text-[10px] font-medium leading-none tabular-nums text-ink-4">
{FICO_MIN}–{FICO_MAX}
</span>
</div>
</div>
<span
className="mt-2 inline-flex items-center justify-center text-[11px] font-bold text-white px-3 py-1 rounded-full w-full"
style={{ backgroundColor: band.color }}
>
{/* Layer 2 — the band this score falls in. A status marker, sized like
one: the colour is the band's own, carried by a dot rather than by a
filled pill the width of the column. */}
<Badge variant="outline" size="lg" className="gap-1.5">
<span
className="h-1.5 w-1.5 rounded-full"
style={{ backgroundColor: band.color }}
aria-hidden="true"
/>
{band.label}
</span>
</Badge>
</div>
<div className="flex-1 space-y-3">
{/* Layer 2 — what the score is worth, and what comes next. */}
<dl className="grid gap-x-6 gap-y-4 min-[420px]:grid-cols-2 sm:grid-cols-1 lg:grid-cols-2">
<div>
<p className="text-[10px] uppercase tracking-wide text-[#9CA3AF] font-medium">Potential Market Value</p>
<p className="text-[16px] font-bold text-[#111827]">{marketRangeFromApp(application)}</p>
<dt className="text-[10px] font-semibold uppercase tracking-[0.06em] text-ink-4">
Potential market value
</dt>
<dd className="mt-1 font-heading text-body-lg font-bold text-ink-1">
{marketRangeFromApp(application)}
</dd>
</div>
<div>
<p className="text-[10px] uppercase tracking-wide text-[#9CA3AF] font-medium">Next Promotion</p>
<p className="text-[14px] font-semibold text-[#0F172A]">{nextTierFromApp(application)}</p>
<dt className="text-[10px] font-semibold uppercase tracking-[0.06em] text-ink-4">
Next promotion
</dt>
<dd className="mt-1 font-heading text-body font-semibold text-ink-1">
{nextTierFromApp(application)}
</dd>
</div>
</div>
</dl>
</div>
);
}
@@ -108,15 +164,26 @@ const initialsFor = (name) => {
return (parts[0][0] + parts[1][0]).toUpperCase();
};
function StatTile({ label, value, suffix, accent }) {
const text = value != null && value !== '' ? value : '—';
/**
* A supporting signal — evidence for the score, not a headline of its own.
*
* Label and value on one line, no border, no tile: five bordered boxes under the
* gauge read as five more metrics competing with it, which is exactly the
* hierarchy this card is meant to establish. Values share one ink colour so no
* single signal shouts louder than the rest.
*/
function SupportingSignal({ label, value, suffix, icon: Icon }) {
const shown = value != null && value !== '' ? value : '—';
return (
<div className="flex flex-col items-center justify-center gap-1 px-2 py-3 rounded-xl bg-white/70 border border-[#E5E7EB]">
<div className="text-[18px] font-bold leading-none" style={{ color: accent }}>
{text}
{suffix && <span className="text-[12px] font-semibold ml-0.5">{suffix}</span>}
</div>
<div className="text-[10px] text-[#6B7280] tracking-wide font-medium">{label}</div>
<div className="flex items-baseline justify-between gap-3">
<dt className="truncate text-caption text-ink-3">{label}</dt>
<dd className="flex shrink-0 items-center font-heading text-body-sm font-bold tabular-nums text-ink-1">
{Icon && <Icon className="mr-1 h-3 w-3 text-krow-yellow" fill="currentColor" aria-hidden="true" />}
<span>
{shown}
{suffix && shown !== '—' && <span className="text-caption font-semibold">{suffix}</span>}
</span>
</dd>
</div>
);
}
@@ -137,37 +204,35 @@ export default function WorkforceReputation({ application }) {
if (!hasData) return null;
return (
<div className="rounded-2xl border border-[#E5E7EB] bg-[#F8FAFC] p-4">
<div className="flex items-center gap-2 mb-3">
<div className="w-7 h-7 rounded-lg bg-[#0A39DF] flex items-center justify-center">
<Shield className="w-4 h-4 text-white" strokeWidth={2.5} />
<div className="rounded-2xl border border-border bg-surface px-4 py-4">
<div className="mb-4 flex items-center gap-2">
<div className="flex h-7 w-7 items-center justify-center rounded-lg bg-krow-blue">
<Shield className="h-4 w-4 text-white" strokeWidth={2.5} />
</div>
<h4 className="text-[12px] font-bold text-[#111827] tracking-wide flex items-center gap-1.5">
<h4 className="flex items-center gap-1.5 text-[12px] font-bold tracking-wide text-ink-1">
WORKFORCE REPUTATION
<TrendingUp className="w-3.5 h-3.5 text-[#0A39DF]" />
<TrendingUp className="h-3.5 w-3.5 text-krow-blue" />
</h4>
</div>
<CareerScoreMini application={application} />
<CareerScorePrimary application={application} />
<div className="grid grid-cols-2 sm:grid-cols-5 gap-2">
<StatTile label="Reliability" value={reliability != null ? reliability : null} suffix="%" accent="#0A39DF" />
<StatTile label="Attendance" value={attendance != null ? attendance : null} suffix="%" accent="#059669" />
<StatTile label="Leadership" value={leadership != null ? leadership : null} suffix="%" accent="#0A39DF" />
<StatTile label="Communication" value={commText} accent="#F59E0B" />
<div className="flex flex-col items-center justify-center gap-1 px-2 py-3 rounded-xl bg-white/70 border border-[#E5E7EB]">
<div className="flex items-center gap-0.5">
<Star className="w-3.5 h-3.5 text-[#F9E547] fill-[#F9E547]" />
<span className="text-[18px] font-bold leading-none text-[#111827]">
{clientRating > 0 ? clientRating.toFixed(1) : '—'}
</span>
</div>
<div className="text-[10px] text-[#6B7280] tracking-wide font-medium">Client Rating</div>
</div>
</div>
{/* Layer 3 — supporting evidence. A hairline is enough to separate it from
the score; another container would make it a second card. */}
<dl className="mt-4 grid grid-cols-1 gap-x-8 gap-y-2 border-t border-border pt-3 min-[420px]:grid-cols-2 lg:grid-cols-3">
<SupportingSignal label="Reliability" value={reliability} suffix="%" />
<SupportingSignal label="Attendance" value={attendance} suffix="%" />
<SupportingSignal label="Leadership" value={leadership} suffix="%" />
<SupportingSignal label="Communication" value={commText} />
<SupportingSignal
label="Client rating"
value={clientRating > 0 ? clientRating.toFixed(1) : null}
icon={Star}
/>
</dl>
{companies.length > 0 && (
<div className="mt-3 pt-3 border-t border-[#E5E7EB]/80">
<div className="mt-3 border-t border-border pt-3">
<p className="text-[10px] font-bold text-[#9CA3AF] tracking-wider mb-2">WORKED</p>
<div className="flex flex-wrap items-center gap-2.5">
{companies.map((c, i) => (