feat(sp6): InboxHeader + CandidateBreakdown
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
// @vitest-environment happy-dom
|
||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
|
import { cleanup, render } from "@testing-library/react";
|
||||||
|
import { CandidateBreakdown } from "./CandidateBreakdown";
|
||||||
|
|
||||||
|
afterEach(() => cleanup());
|
||||||
|
|
||||||
|
describe("CandidateBreakdown", () => {
|
||||||
|
it("renders per-field scores", () => {
|
||||||
|
const { container } = render(
|
||||||
|
<CandidateBreakdown
|
||||||
|
score={92}
|
||||||
|
breakdown={{ patient: 1, date: 1, amount: 1, provider: 1 }}
|
||||||
|
tier="strong"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
expect(container.textContent).toMatch(/patient/i);
|
||||||
|
expect(container.textContent).toMatch(/date/i);
|
||||||
|
expect(container.textContent).toMatch(/amount/i);
|
||||||
|
expect(container.textContent).toMatch(/provider/i);
|
||||||
|
expect(container.textContent).toContain("92");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import type { ScoreBreakdown, ScoreTier } from "@/lib/inbox-api";
|
||||||
|
|
||||||
|
const FIELDS: Array<{ key: keyof ScoreBreakdown; label: string; weight: number }> = [
|
||||||
|
{ key: "patient", label: "Patient", weight: 40 },
|
||||||
|
{ key: "date", label: "Date", weight: 25 },
|
||||||
|
{ key: "amount", label: "Amount", weight: 20 },
|
||||||
|
{ key: "provider", label: "Provider", weight: 15 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function CandidateBreakdown({
|
||||||
|
score,
|
||||||
|
breakdown,
|
||||||
|
tier,
|
||||||
|
}: {
|
||||||
|
score: number;
|
||||||
|
breakdown: ScoreBreakdown;
|
||||||
|
tier: ScoreTier;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="font-mono" style={{ fontSize: 11 }}>
|
||||||
|
<div className="flex items-baseline justify-between mb-2">
|
||||||
|
<span
|
||||||
|
className="uppercase"
|
||||||
|
style={{ color: "var(--tt-ink-dim)", letterSpacing: "0.12em" }}
|
||||||
|
>
|
||||||
|
Score
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="tabular-nums"
|
||||||
|
style={{ color: "var(--tt-amber)", fontSize: 24 }}
|
||||||
|
>
|
||||||
|
{score}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ul className="space-y-1">
|
||||||
|
{FIELDS.map((f) => (
|
||||||
|
<li key={f.key} className="flex justify-between">
|
||||||
|
<span style={{ color: "var(--tt-ink)" }}>{f.label}</span>
|
||||||
|
<span
|
||||||
|
className="tabular-nums"
|
||||||
|
style={{ color: "var(--tt-ink-dim)" }}
|
||||||
|
>
|
||||||
|
{Math.round(breakdown[f.key] * f.weight)} / {f.weight}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<p
|
||||||
|
className="mt-3 uppercase"
|
||||||
|
style={{ color: "var(--tt-ink-dim)", letterSpacing: "0.12em", fontSize: 10 }}
|
||||||
|
>
|
||||||
|
{tier}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// @vitest-environment happy-dom
|
||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
|
import { cleanup, render } from "@testing-library/react";
|
||||||
|
import { InboxHeader } from "./InboxHeader";
|
||||||
|
|
||||||
|
afterEach(() => cleanup());
|
||||||
|
|
||||||
|
describe("InboxHeader", () => {
|
||||||
|
it("renders the date and counts", () => {
|
||||||
|
const { container } = render(<InboxHeader needEyesCount={6} doneTodayCount={23} />);
|
||||||
|
expect(container.textContent).toMatch(/6.*items need eyes/);
|
||||||
|
expect(container.textContent).toMatch(/23.*done today/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
export function InboxHeader({
|
||||||
|
needEyesCount,
|
||||||
|
doneTodayCount,
|
||||||
|
}: {
|
||||||
|
needEyesCount: number;
|
||||||
|
doneTodayCount: number;
|
||||||
|
}) {
|
||||||
|
const now = new Date();
|
||||||
|
const day = now.toLocaleDateString("en-US", { weekday: "short" }).toUpperCase();
|
||||||
|
const time = now.toLocaleTimeString("en-US", {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header
|
||||||
|
className="sticky top-0 z-10 px-6 py-4"
|
||||||
|
style={{
|
||||||
|
background: "var(--tt-bg)",
|
||||||
|
borderBottom: "1px solid var(--tt-bg-elev)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
className="tracking-tight"
|
||||||
|
style={{
|
||||||
|
color: "var(--tt-ink)",
|
||||||
|
fontFamily: "'Inter Tight', system-ui, sans-serif",
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: 22,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
INBOX{" "}
|
||||||
|
<span
|
||||||
|
className="font-mono"
|
||||||
|
style={{ color: "var(--tt-amber)", fontSize: 16 }}
|
||||||
|
>
|
||||||
|
· {day} {time}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p
|
||||||
|
className="font-mono mt-1"
|
||||||
|
style={{ color: "var(--tt-ink-dim)", fontSize: 11, letterSpacing: "0.05em" }}
|
||||||
|
>
|
||||||
|
<span style={{ color: "var(--tt-amber)" }}>{needEyesCount}</span> items need eyes ·{" "}
|
||||||
|
<span style={{ color: "var(--tt-ink)" }}>{doneTodayCount}</span> done today
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user