Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/components/clinical-dashboard/ClinicalSidebar.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,9 +52,9 @@ const sidebarToolItems = [
{ id: "documents", label: "Documents", icon: FileText, href: "/?mode=documents" },
{ id: "services", label: "Services", icon: ClipboardList, href: "/services" },
{ id: "forms", label: "Forms", icon: FileText, href: "/forms" },
{ id: "favourites", label: "Faves", icon: Heart, href: "/favourites" },
{ id: "differentials", label: "Diffs", icon: BrainCircuit, href: "/differentials" },
{ id: "prescribing", label: "Meds", icon: Pill, href: "/?mode=prescribing" },
{ id: "favourites", label: "Favourites", icon: Heart, href: "/favourites" },
{ id: "differentials", label: "Differentials", icon: BrainCircuit, href: "/differentials" },
{ id: "prescribing", label: "Medications", icon: Pill, href: "/?mode=prescribing" },
{ id: "tools", label: "Tools", icon: Wrench, href: "/?mode=tools" },
] as const;

Expand Down
5 changes: 3 additions & 2 deletions src/components/clinical-dashboard/favourites-hub.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,7 +135,7 @@ export function FavouritesHub({
{[
{ label: "Items", value: itemCount, icon: Heart },
{ label: "Sets", value: setCount, icon: Folder },
{ label: "Active", value: activeFilterCount, icon: Filter },
{ label: "Filters", value: activeFilterCount, icon: Filter },
].map((stat) => {
const Icon = stat.icon;
return (
Expand DownExpand Up@@ -490,7 +490,8 @@ function FavouriteSetRow({
selected ? "text-[color:var(--clinical-accent)]" : "text-[color:var(--text-muted)]",
)}
>
{favouriteSet.count} items{compact ? "" : ` · ${favouriteSet.meta}`}
{favouriteSet.count} {favouriteSet.count === 1 ? "item" : "items"}
{compact ? "" : ` · ${favouriteSet.meta}`}
</span>
</span>
<ChevronDown className="-rotate-90 h-4 w-4 text-[color:var(--text-soft)]" />
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,25 +94,27 @@ export const favouriteItems: FavouriteItem[] = [
},
];

const countItemsInSet = (title: string) => favouriteItems.filter((item) => item.set === title).length;

export const favouriteSets: FavouriteSet[] = [
{
id: "ward-round",
title: "Ward round",
count: 12,
count: countItemsInSet("Ward round"),
meta: "Medication pages, renal checks, forms",
keywords: "ward round acamprosate lithium renal mht forms",
},
{
id: "prescribing-safety",
title: "Prescribing safety",
count: 9,
count: countItemsInSet("Prescribing safety"),
meta: "Dose limits, pregnancy, renal cautions",
keywords: "prescribing safety dose pregnancy renal qt interactions",
},
{
id: "clozapine-clinic",
title: "Clozapine clinic",
count: 7,
count: countItemsInSet("Clozapine clinic"),
meta: "Monitoring, ANC table, counselling",
keywords: "clozapine clinic monitoring anc table counselling",
},
Expand Down
4 changes: 2 additions & 2 deletions src/components/clinical-dashboard/master-search-header.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -355,7 +355,6 @@ export function MasterSearchHeader({
searchMode === "prescribing" ? medicationModeActionItems : modeActionItemsFor(actionMenuSetId);
const actionMenuTitle = selectedAppMode.label;
const actionMenuButtonLabel = `Open ${selectedAppMode.label.toLowerCase()} options`;
const isStandaloneModeHomeHeader = Boolean(desktopHomeComposerSlotId);
const useMobileBackControl = mobileLeadingAction === "back";

function currentUsesScopeSheet() {
Expand DownExpand Up@@ -887,7 +886,8 @@ export function MasterSearchHeader({
const usesAnswerFooterStyle = isAnswerFooterComposer && !isDesktopHomeComposer;
const usesMobileBottomStyle = isMobileBottomComposer && !isDesktopHomeComposer;
const usesUniversalFooterStyle = usesAnswerFooterStyle || (usesMobileBottomStyle && usesPhoneSearchLayout);
const usesSendAffordance = usesAnswerFooterStyle || (isStandaloneModeHomeHeader && searchMode === "differentials");
// Only the Answer chat composer uses the send affordance; every search-mode home uses the magnifier.
const usesSendAffordance = usesAnswerFooterStyle;
const composerPlaceholder =
usesMobileBottomStyle && searchMode === "differentials" ? "Search a presentation" : queryPlaceholder;

Expand Down
77 changes: 44 additions & 33 deletions src/components/differentials/differential-detail-page.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -87,6 +87,13 @@ function statusLabel(status: DifferentialRecord["status"]) {
return "Routine";
}

/** Maps a related node's likelihood to its own severity tag, mirroring the record-status tones. */
function likelihoodTag(likelihood: DifferentialRecord["related"][number]["likelihood"]) {
if (likelihood === "must-not-miss") return { label: "Emergent", className: statusTone.emergent };
if (likelihood === "possible") return { label: "Urgent", className: statusTone.urgent };
return { label: "Review", className: statusTone.routine };
}

function SectionRow({ section }: { section: DifferentialSection }) {
const Icon = sectionIcons[section.tone];
const meta = rowMeta[section.tone];
Expand DownExpand Up@@ -202,29 +209,28 @@ function RelatedDiagnoses({ record }: { record: DifferentialRecord }) {
Related diagnoses
</h2>
<ul className="mt-3 grid gap-2">
{record.related.slice(0, 4).map((node) => (
<li key={node.id} className="flex items-center justify-between gap-2 text-xs font-bold">
<span className="min-w-0 truncate text-[color:var(--text-heading)]">{node.label}</span>
<span
className={cn(
"shrink-0 rounded-md border px-1.5 py-0.5 text-[10px] font-extrabold uppercase",
node.likelihood === "must-not-miss"
? "border-[color:var(--danger)]/20 bg-[color:var(--danger-soft)] text-[color:var(--danger)]"
: node.likelihood === "possible"
? "border-[color:var(--warning)]/20 bg-[color:var(--warning-soft)] text-[color:var(--warning)]"
: "border-[color:var(--border)] bg-[color:var(--surface-subtle)] text-[color:var(--text-muted)]",
)}
>
{node.likelihood === "must-not-miss" ? "Emergent" : node.likelihood === "possible" ? "Urgent" : "Review"}
</span>
</li>
))}
{record.related.slice(0, 4).map((node) => {
const tag = likelihoodTag(node.likelihood);
return (
<li key={node.id} className="flex items-center justify-between gap-2 text-xs font-bold">
<span className="min-w-0 truncate text-[color:var(--text-heading)]">{node.label}</span>
<span
className={cn(
"shrink-0 rounded-md border px-1.5 py-0.5 text-[10px] font-extrabold uppercase",
tag.className,
)}
>
{tag.label}
</span>
</li>
);
})}
</ul>
<Link
href="#related"
className="mt-3 inline-flex items-center gap-1 text-xs font-bold text-[color:var(--clinical-accent)]"
>
View all related ({record.related.length + 8})
View all related ({record.related.length})
<ChevronRight className="h-3.5 w-3.5" aria-hidden />
</Link>
</section>
Expand All@@ -250,7 +256,16 @@ function CurrentPresentation({ record }: { record: DifferentialRecord }) {
}

function CompareBasket({ record }: { record: DifferentialRecord }) {
const items = [record.title, ...record.related.slice(0, 2).map((node) => node.label)];
const items = [
{
id: "self",
label: record.title,
tag: { label: statusLabel(record.status), className: statusTone[record.status] },
},
...record.related
.slice(0, 2)
.map((node) => ({ id: node.id, label: node.label, tag: likelihoodTag(node.likelihood) })),
];

return (
<section className="hidden rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-4 shadow-[var(--shadow-inset)] lg:block">
Expand All@@ -265,15 +280,20 @@ function CompareBasket({ record }: { record: DifferentialRecord }) {
<ul className="mt-3 grid gap-2">
{items.map((item) => (
<li
key={item}
key={item.id}
className="flex items-center justify-between gap-2 text-xs font-bold text-[color:var(--text-heading)]"
>
<span className="inline-flex min-w-0 items-center gap-2">
<BrainCircuit className="h-4 w-4 shrink-0 text-[color:var(--text-muted)]" aria-hidden />
<span className="truncate">{item}</span>
<span className="truncate">{item.label}</span>
</span>
<span className="rounded-md border border-[color:var(--danger)]/20 bg-[color:var(--danger-soft)] px-1.5 py-0.5 text-[10px] font-extrabold uppercase text-[color:var(--danger)]">
{statusLabel(record.status)}
<span
className={cn(
"shrink-0 rounded-md border px-1.5 py-0.5 text-[10px] font-extrabold uppercase",
item.tag.className,
)}
>
{item.tag.label}
</span>
</li>
))}
Expand DownExpand Up@@ -382,16 +402,6 @@ function HeaderChrome() {
>
<ChevronRight className="h-5 w-5 rotate-180" aria-hidden />
</Link>
<div className="hidden items-center gap-2 rounded-full border border-[color:var(--border-lux)] bg-[color:var(--surface)] px-3 py-1.5 shadow-[var(--shadow-inset)] sm:flex">
<span className="grid h-8 w-8 place-items-center rounded-full bg-[color:var(--clinical-accent)] text-[color:var(--clinical-accent-contrast)]">
<GitCompareArrows className="h-4 w-4" aria-hidden />
</span>
<div className="leading-tight">
<p className="text-[10px] font-bold uppercase text-[color:var(--text-muted)]">Mode</p>
<p className="text-sm font-extrabold text-[color:var(--text-heading)]">Differentials</p>
</div>
<ChevronDown className="h-4 w-4 text-[color:var(--text-muted)]" aria-hidden />
</div>
</div>
<div className="flex items-center gap-2">
<span className="hidden rounded-lg border border-[color:var(--success)]/20 bg-[color:var(--success-soft)] px-3 py-2 text-xs font-bold text-[color:var(--success)] sm:inline-flex">
Expand DownExpand Up@@ -486,6 +496,7 @@ export function DifferentialDetailPage({ record }: { record: DifferentialRecord
<div className="grid gap-5 lg:grid-cols-[minmax(0,1fr)_24rem] xl:grid-cols-[minmax(0,1fr)_27rem]">
<section className="grid gap-4">
<SafetySnapshot record={record} />
<div className="h-44 lg:hidden" aria-hidden />
<div className="overflow-hidden rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] shadow-[var(--shadow-inset)]">
{record.sections.map((section) => (
<SectionRow key={section.id} section={section} />
Expand Down
16 changes: 6 additions & 10 deletions src/components/differentials/differential-stream-page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import Link from "next/link";
import { ArrowLeft, ArrowRight, CircleHelp, FileText } from "lucide-react";
import { ArrowLeft, ArrowRight, FileText } from "lucide-react";
import { appModeHomeHref } from "@/lib/app-modes";

import {
Expand DownExpand Up@@ -67,9 +67,9 @@ export function DifferentialStreamPage({ stream, query = "" }: DifferentialStrea
>
<h3 className="text-sm font-bold text-[color:var(--text-heading)]">{card.title}</h3>
<p className="mt-1 text-sm text-[color:var(--text-muted)]">{card.description}</p>
<ul className="mt-2 space-y-1 text-xs leading-6 text-[color:var(--text-soft)]">
<ul className="mt-2 flex flex-col gap-1 text-xs leading-6 text-[color:var(--text-soft)]">
{card.examples.map((example) => (
<li key={example} className="inline-flex items-start gap-2">
<li key={example} className="flex items-start gap-2">
<FileText className="mt-0.5 h-4 w-4 text-[color:var(--text-muted)]" aria-hidden />
{example}
</li>
Expand All@@ -82,14 +82,10 @@ export function DifferentialStreamPage({ stream, query = "" }: DifferentialStrea

<section className="grid gap-3 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-4 sm:grid-cols-[1fr_auto] sm:grid">
<div className="grid gap-2">
<h2 className="text-sm font-bold text-[color:var(--text-heading)]">Stream helper</h2>
<h2 className="text-sm font-bold text-[color:var(--text-heading)]">Keep exploring</h2>
<p className="text-sm leading-6 text-[color:var(--text-muted)]">
This stream contains differential diagnosis content only. Use it to move from presentation clues to
diagnosis detail pages without mixing in service or referral records.
</p>
<p className="inline-flex items-center gap-2 text-xs font-bold uppercase tracking-[0.08em] text-[color:var(--text-soft)]">
<CircleHelp className="h-4 w-4" aria-hidden />
Use for workflow verification only
Return to the differentials home to start from a different presentation, or open search to look up another
differential.
</p>
</div>
<div className="flex flex-wrap gap-2">
Expand Down
11 changes: 10 additions & 1 deletion src/components/forms/form-detail-page.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,6 +150,15 @@ function summaryCardsFor(form: FormRecord): ServiceSummaryCard[] {
];
}

function joinNotes(notes: string[] | null | undefined) {
if (!notes?.length) return undefined;
return notes
.map((note) => note.trim())
.filter(Boolean)
.map((note) => (/[.!?]$/.test(note) ? note : `${note}.`))
.join(" ");
}

function detailRowsFor(form: FormRecord) {
const referralRows = form.referralInfo?.length
? form.referralInfo
Expand All@@ -162,7 +171,7 @@ function detailRowsFor(form: FormRecord) {

return [
...referralRows,
{ label: "Verification", value: form.verification?.notes?.join(" ") },
{ label: "Verification", value: joinNotes(form.verification?.notes) },
{ label: "Related pathway", value: form.route },
].filter((row) => hasText(row.value));
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/services/services-home-page.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@ import { defaultServiceSlug, serviceRecords } from "@/lib/services";

const taskCards: ModeHomeAction[] = [
{
title: "Find a service",
title: "Search services",
description: "Search by need, catchment, provider, or keyword.",
icon: FileSearch,
href: appModeHomeHref("services", { focus: true }),
Expand DownExpand Up@@ -43,7 +43,7 @@ const commonPathways: ModeHomePill[] = [
href: appModeHomeHref("services", { query: "crisis support services", focus: true, run: true }),
},
{
label: "ATSI-specific",
label: "Aboriginal and Torres Strait Islander",
tone: "info",
href: appModeHomeHref("services", {
query: "Aboriginal Torres Strait Islander services",
Expand Down
12 changes: 6 additions & 6 deletions src/lib/services.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,7 +82,7 @@ export const serviceRecords: ServiceRecord[] = [
subtitle: "Urgent contact, crisis response, or acute support pathway.",
statusChips: [
{ label: "Crisis / urgent", tone: "danger" },
{ label: "ATSI-specific", tone: "info" },
{ label: "Aboriginal and Torres Strait Islander", tone: "info" },
{ label: "Local confirmation", tone: "warning" },
],
primaryContact: {
Expand All@@ -106,7 +106,7 @@ export const serviceRecords: ServiceRecord[] = [
},
],
route: "Self phone referral",
eligibility: "ATSI callers",
eligibility: "Aboriginal and Torres Strait Islander callers",
cost: "Free",
referral: "Self referral by phone. Escalate emergency medical danger through emergency services.",
location: "Statewide / national",
Expand All@@ -120,7 +120,7 @@ export const serviceRecords: ServiceRecord[] = [
{
id: "eligibility",
label: "Eligibility",
title: "ATSI callers",
title: "Aboriginal and Torres Strait Islander callers",
detail: "See details",
},
{
Expand All@@ -140,15 +140,15 @@ export const serviceRecords: ServiceRecord[] = [
{ label: "Primary route", value: "Contact: 13 92 76\nSelf phone referral" },
{ label: "Phone", value: "13 92 76" },
{ label: "Email", value: "None listed" },
{ label: "Provider", value: "ATSI crisis support service referenced by WACHS" },
{ label: "Provider", value: "Aboriginal and Torres Strait Islander crisis support service referenced by WACHS" },
{ label: "Region", value: "Statewide / national" },
{ label: "Patient group", value: "Aboriginal and Torres Strait Islander people" },
{ label: "Hours", value: "Not publicly stated" },
{ label: "Cost / funding", value: "Free" },
],
bestUse: "Culturally safe crisis phone support; escalate emergency danger elsewhere.",
criteria: [
{ label: "ATSI support need", tone: "meet" },
{ label: "Aboriginal and Torres Strait Islander support need", tone: "meet" },
{ label: "Crisis support pathway appropriate", tone: "meet" },
{ label: "Phone referral available", tone: "meet" },
{ label: "Emergency medical danger present", tone: "reject" },
Expand DownExpand Up@@ -411,7 +411,7 @@ export const serviceRecords: ServiceRecord[] = [
title: "State-wide Specialist Aboriginal Mental Health Service",
subtitle: "Great Southern WACHS service combining cultural and clinical mental health expertise.",
statusChips: [
{ label: "ATSI-specific", tone: "info" },
{ label: "Aboriginal and Torres Strait Islander", tone: "info" },
{ label: "Regional WA", tone: "success" },
{ label: "Local confirmation", tone: "warning" },
],
Expand Down
Loading