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
4 changes: 4 additions & 0 deletions src/components/cme/cme-dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { CmeYearInWeeks } from "@/components/cme/cme-year-in-weeks";
import { WorkColumns } from "@/components/work-frame/work-desktop-layout";
import { Button } from "@/components/ui/button";
import { NewWorkModeOnly } from "@/components/work-mode-launch/work-mode-launch-provider";
import { WorkSetupRow } from "@/components/work-setup/work-setup-row";
import { addDays, expandEvents } from "@/lib/calendar/calendar-event";
import { cmeCalendarEvents } from "@/lib/cme/calendar-events";
import { buildCmeCatchUpPlan } from "@/lib/cme/catch-up-plan";
Expand Down Expand Up @@ -637,6 +638,9 @@ export function CmeDashboard({
the dates and the year's details on the right (11 Oct 2026 layout audit). */}
<WorkColumns>
<div>
<NewWorkModeOnly>
<WorkSetupRow />
</NewWorkModeOnly>
{summary}
{nextStepNode}
{toLog}
Expand Down
61 changes: 61 additions & 0 deletions src/components/mode-kit/work-empty-line.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import Link from "next/link";
import type { ReactNode } from "react";

import { focusRing } from "@/components/card-recipes";
import { cn } from "@/components/ui-primitives";

/**
* The one inline empty state for a row or a short block in a work area (site
* review 12 Oct 2026, item 2): a plain sentence and, when there is somewhere
* real to go, one action. Before this a row could end at "Not set up for this
* hospital" with nothing to tap.
*
* The page-sized empty state stays `WorkEmpty` / `WorkStateNotice`; this is
* for a line inside a card. With no action it draws the sentence only, so a
* caller never invents a destination to fill the slot.
*/
export type WorkEmptyLineAction =
| { readonly label: string; readonly href: string; readonly onClick?: never; readonly testId?: string }
| { readonly label: string; readonly onClick: () => void; readonly href?: never; readonly testId?: string };

export const workEmptyLineActionClass =
"inline-flex min-h-12 shrink-0 items-center rounded-sm px-1 text-sm font-semibold text-[color:var(--mode-identity)] no-underline";

export function WorkEmptyLine({
children,
action,
className,
testId,
}: {
/** The sentence: plain words that say what is missing. */
readonly children: ReactNode;
readonly action?: WorkEmptyLineAction;
readonly className?: string;
readonly testId?: string;
}) {
return (
<div
className={cn("flex min-w-0 flex-wrap items-center justify-between gap-x-3", className)}
data-testid={testId}
data-work-empty-line=""
>
<p className="m-0 min-w-0 flex-1 basis-40 py-1 text-sm break-words text-[color:var(--text-muted)]">{children}</p>
{action ? (
action.href !== undefined ? (
<Link href={action.href} data-testid={action.testId} className={cn(workEmptyLineActionClass, focusRing)}>
{action.label}
</Link>
) : (
<button
type="button"
onClick={action.onClick}
data-testid={action.testId}
className={cn(workEmptyLineActionClass, focusRing)}
>
{action.label}
</button>
)
) : null}
</div>
);
}
40 changes: 39 additions & 1 deletion src/components/mode-kit/work-sign-in-notice.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"use client";

import type { LucideIcon } from "lucide-react";
import { LogIn, type LucideIcon } from "lucide-react";
import { useEffect, useState, type ReactNode } from "react";

import { AccountSetupDialog } from "@/components/clinical-dashboard/account-setup-dialog";
import { WorkButton } from "@/components/mode-kit/work";
import { WorkStateNotice } from "@/components/mode-kit/work-state";
import { useAuthSession } from "@/lib/supabase/client";

Expand Down Expand Up @@ -31,6 +32,43 @@ function RefreshOnSignIn({ onSignedIn }: { readonly onSignedIn: () => void }) {
return null;
}

/**
* The slim signed-out line: one sentence and Sign in, in a flat hairline card
* at the top of the page content (site review 12 Oct 2026, item 2). For a page
* whose own content still shows signed out (On Call keeps its public crisis
* lines and examples below), so the sign-in ask does not push them half a
* screen down the way the full `WorkSignInNotice` card did.
*/
export function WorkSignInLine({
title,
signInLabel = "Sign in",
signInTestId,
testId,
}: {
readonly title: ReactNode;
readonly signInLabel?: string;
readonly signInTestId?: string;
readonly testId?: string;
}) {
const [open, setOpen] = useState(false);
return (
<>
<div
className="work-card flex min-w-0 flex-wrap items-center justify-between gap-x-3 gap-y-1 py-1 pr-1.5 pl-3"
data-testid={testId}
>
<p className="m-0 min-w-0 flex-1 basis-44 py-2 text-sm font-medium break-words text-[color:var(--text-heading)]">
{title}
</p>
<WorkButton icon={LogIn} onClick={() => setOpen(true)} testId={signInTestId}>
{signInLabel}
</WorkButton>
</div>
{open ? <AccountSetupDialog open onClose={() => setOpen(false)} /> : null}
</>
);
}

/**
* The shared signed-out state with the way in: "Sign in" opens the app's own
* account dialog (there is no sign-in page), the same in every work area.
Expand Down
10 changes: 7 additions & 3 deletions src/components/my-day/my-day-today-cards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Clock,
FileText,
GraduationCap,
ListChecks,
Moon,
Phone,
Shield,
Expand Down Expand Up @@ -751,10 +752,13 @@ const MY_DAY_QUICK_ACTIONS: readonly MyDayQuickAction[] = [
{ label: "Roster", href: "/roster", icon: CalendarDays, testId: "my-day-qa-roster", mode: "roster" },
];

/** During on call (the mockup's 03:10 screen): Handover and Remind me take CPD's and Roster's places. */
/**
* During on call the hero already offers Log a call and Handover, so the tiles
* offer what it does not (site review 12 Oct 2026, item 7).
*/
const MY_DAY_ON_CALL_ACTIONS: readonly MyDayQuickAction[] = [
MY_DAY_QUICK_ACTIONS[0]!,
{ label: "Handover", href: "/on-call/handover", icon: FileText, testId: "my-day-qa-handover", mode: "on-call" },
{ label: "Playbook", href: "/on-call/playbook", icon: ListChecks, testId: "my-day-qa-playbook", mode: "on-call" },
{ label: "Contacts", href: "/on-call/contacts", icon: Phone, testId: "my-day-qa-contacts", mode: "on-call" },
MY_DAY_QUICK_ACTIONS[2]!,
{ label: "Remind me", icon: Bell, testId: "my-day-qa-remind", mode: "my-day" },
];
Expand Down
8 changes: 5 additions & 3 deletions src/components/on-call/kit/handbook-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type {
HospitalHandbookStatus,
} from "@/components/on-call/use-hospital-handbook";
import { WorkButton } from "@/components/mode-kit/work";
import { WorkSignInNotice } from "@/components/mode-kit/work-sign-in-notice";
import { WorkSignInLine } from "@/components/mode-kit/work-sign-in-notice";
import { WorkStateNotice } from "@/components/mode-kit/work-state";
import { cn } from "@/components/ui-primitives";

Expand All @@ -27,7 +27,7 @@ const ON_CALL_SERVICE_HREF = "/on-call/service";
* (`tests/on-call-hub-wording.test.ts`).
*/
const SIGN_IN_TITLES: Partial<Record<HospitalHandbookStatus, string>> = {
"signed-out": "Hospital numbers are for signed-in members.",
"signed-out": "Sign in to see your hospital's numbers",
expired: "Your session ended. Sign in again to see your hospital's numbers.",
};

Expand Down Expand Up @@ -59,7 +59,9 @@ export function OnCallHandbookState({
}) {
const testId = `on-call-handbook-state-${handbook.status}`;
const signIn = SIGN_IN_TITLES[handbook.status];
if (signIn) return <WorkSignInNotice icon={ON_CALL_HUB_PAGE_ICONS[page]} title={signIn} testId={testId} />;
// One slim line at the top, not a page-sized card (site review 12 Oct 2026, item 2): the
// public crisis lines and the page's examples below it stay where they were.
if (signIn) return <WorkSignInLine title={signIn} testId={testId} />;
switch (handbook.status) {
case "ready":
return null;
Expand Down
10 changes: 10 additions & 0 deletions src/components/on-call/kit/state-label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ function words(state: OnCallRowState): string {
}
}

/**
* The one action a "Not set up for this hospital" line offers (site review 12 Oct 2026, item 2):
* the service handbook page, where the service's own entries are kept. It is an existing page;
* nothing new is promised.
*/
export const ON_CALL_SERVICE_HANDBOOK_ACTION = {
label: "Service handbook",
href: "/on-call/service",
} as const;

export function OnCallStateLabel({ state }: { readonly state: OnCallRowState }) {
const warning = state.kind === "removed";
return (
Expand Down
11 changes: 8 additions & 3 deletions src/components/on-call/now/right-now.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { OnCallTrackBar } from "@/components/on-call/kit/track-bar";
import { onCallOutlineButton } from "@/components/on-call/kit/calm";
import { OnCallDialSheet, onCallCallRoute } from "@/components/on-call/kit/dial-sheet";
import { OnCallGroupActionControl } from "@/components/on-call/kit/grouped-list";
import { OnCallStateLabel } from "@/components/on-call/kit/state-label";
import { ON_CALL_SERVICE_HANDBOOK_ACTION, OnCallStateLabel } from "@/components/on-call/kit/state-label";
import { WorkEmptyLine } from "@/components/mode-kit/work-empty-line";
import { useOnCallYouCalledAt } from "@/components/on-call/kit/use-you-called";
import { useOnCallHospitalPhone } from "@/components/on-call/call/call-device-stores";
import { modeHeadingText, modeNumberText, modeSecondaryText } from "@/components/mode-kit/type";
Expand Down Expand Up @@ -153,9 +154,13 @@ export function NowRightNow({
) : null}
</div>
) : (
<p className="border-t border-[color:var(--border)] pt-2" data-testid="on-call-now-right-now-not-set-up">
<WorkEmptyLine
className="border-t border-[color:var(--border)] pt-1"
testId="on-call-now-right-now-not-set-up"
action={ON_CALL_SERVICE_HANDBOOK_ACTION}
>
<OnCallStateLabel state={{ kind: "not-set-up" }} />
</p>
</WorkEmptyLine>
)}

{cover ? (
Expand Down
25 changes: 23 additions & 2 deletions src/components/on-call/now/shift-lists.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { useId, useState } from "react";
import { focusRing } from "@/components/card-recipes";
import { modeInsetHairline, modeModuleSurface, modePressable, modeRowHeight } from "@/components/mode-kit/recipes";
import { OnCallTrackBar } from "@/components/on-call/kit/track-bar";
import { OnCallStateLabel } from "@/components/on-call/kit/state-label";
import { ON_CALL_SERVICE_HANDBOOK_ACTION, OnCallStateLabel } from "@/components/on-call/kit/state-label";
import { WorkEmptyLine } from "@/components/mode-kit/work-empty-line";
import { onCallLeadingIcon } from "@/components/on-call/kit/calm";
import { modeNameText, modeNumberText, modeSecondaryText } from "@/components/mode-kit/type";
import { OnCallNextShift } from "@/components/on-call/on-call-next-shift";
Expand Down Expand Up @@ -116,11 +117,14 @@ export function NowShiftLists({
shifts,
items,
now,
handbookReady = true,
}: {
readonly context: OnCallShiftContext;
readonly shifts: RosterShiftsState;
readonly items: readonly HandbookItem[];
readonly now: Date;
/** False while the handbook is loading, signed out or unavailable: an empty list is then unconfirmed. */
readonly handbookReady?: boolean;
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
Resolved
}) {
const [open, setOpen] = useState(false);
const [ticks, setTicks] = useState<{ readonly shiftKey: string; readonly ids: ReadonlySet<string> }>(() => ({
Expand Down Expand Up @@ -176,8 +180,12 @@ export function NowShiftLists({
className="w-16 shrink-0"
/>
</span>
) : (
) : handbookReady ? (
<OnCallStateLabel state={{ kind: "not-set-up" }} />
) : (
<span data-testid="on-call-now-checklists-pending" className="text-xs text-[color:var(--text-muted)]">
Not available right now
</span>
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
Resolved
)}
</span>
<ChevronRight aria-hidden="true" className="size-icon-md shrink-0 text-[color:var(--text-muted)]" />
Expand All @@ -201,6 +209,19 @@ export function NowShiftLists({
{lists.map((list) => (
<TickList key={list.key} list={list} ticked={ticked} onToggle={toggle} />
))}
{hasLists ? null : handbookReady ? (
<WorkEmptyLine
className="px-3"
testId="on-call-now-checklists-empty"
action={ON_CALL_SERVICE_HANDBOOK_ACTION}
>
Your service has not added shift lists for this hospital yet.
</WorkEmptyLine>
) : (
<WorkEmptyLine className="px-3" testId="on-call-now-checklists-empty">
Shift lists are not available right now.
</WorkEmptyLine>
)}
{hasLists ? (
<div className="flex min-w-0 items-center justify-between gap-3 px-3">
<p className={modeSecondaryText}>Nothing is saved</p>
Expand Down
13 changes: 12 additions & 1 deletion src/components/on-call/on-call-home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { useRosterShifts } from "@/components/roster/use-roster-shifts";
import { OnCallEmptyState } from "@/components/on-call/kit/empty-state";
import { cn, eyebrowText } from "@/components/ui-primitives";
import { NewWorkModeOnly } from "@/components/work-mode-launch/work-mode-launch-provider";
import { WorkSetupRow } from "@/components/work-setup/work-setup-row";
import { onCallCallNowScenarios, onCallCallNowSteps } from "@/lib/on-call/call-now";
import { useOnCallEntries } from "@/lib/on-call/entry-store";
import { pinnedEmergencyEntries } from "@/lib/on-call/handbook-items";
Expand Down Expand Up @@ -337,6 +338,10 @@ function OnCallHomeLive({ now: pinnedNow }: { now?: Date } = {}) {
<>
<NowEmergencyPin handbook={handbook} pins={pins} now={now} />
{numbersOnScreen ? null : <NowCrisisLines now={now} />}
{/* Never between the emergency pin and crisis lines. When hospital numbers are on screen the crisis lines sit at the foot of the page, as on main. */}
<NewWorkModeOnly>
<WorkSetupRow />
</NewWorkModeOnly>
Comment thread
gitar-bot[bot] marked this conversation as resolved.
Resolved
</>
}
nowSurface="own"
Expand Down Expand Up @@ -415,7 +420,13 @@ function OnCallHomeLive({ now: pinnedNow }: { now?: Date } = {}) {
12 Oct 2026), share the tools' card: the rest live where they belong (Roster, Handbook,
Starting here). */}
<NowShiftShortcuts>
<NowShiftLists context={context} shifts={shifts} items={handbookItems} now={now} />
<NowShiftLists
context={context}
shifts={shifts}
items={handbookItems}
now={now}
handbookReady={ready || handbook.status === "no-service"}
/>
</NowShiftShortcuts>
<NowShiftPulseCard now={now} />
{/* A week before a new job starts until the end of its first week. */}
Expand Down
7 changes: 5 additions & 2 deletions src/components/on-call/whos-on/whos-on-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import { OnCallGroupedList } from "@/components/on-call/kit/grouped-list";
import { onCallCrisisLinesCompact, OnCallHandbookState } from "@/components/on-call/kit/handbook-state";
import { OnCallHospitalLine } from "@/components/on-call/kit/hospital-line";
import { OnCallHubPageFrame } from "@/components/on-call/kit/hub-page-frame";
import { OnCallStateLabel } from "@/components/on-call/kit/state-label";
import { ON_CALL_SERVICE_HANDBOOK_ACTION, OnCallStateLabel } from "@/components/on-call/kit/state-label";
import { WorkEmptyLine } from "@/components/mode-kit/work-empty-line";
import { allocateOnCallGroupSlug, onCallGroupAnchorId } from "@/components/on-call/on-call-page-anchors";
import { onCallWhosOnSections } from "@/components/on-call/on-call-page-sections";
import { useHospitalHandbook } from "@/components/on-call/use-hospital-handbook";
Expand Down Expand Up @@ -136,7 +137,9 @@ export function OnCallWhosOnPage({ now: pinned }: { now?: Date } = {}) {
{myTeamMissing ? (
<OnCallGroupedList eyebrow={myTeam ?? undefined} headerIcon={Users} testId="on-call-whos-on-my-team-empty">
<li className="flex min-h-12 items-center px-3">
<OnCallStateLabel state={{ kind: "not-set-up" }} />
<WorkEmptyLine className="w-full" action={ON_CALL_SERVICE_HANDBOOK_ACTION}>
<OnCallStateLabel state={{ kind: "not-set-up" }} />
</WorkEmptyLine>
</li>
</OnCallGroupedList>
) : null}
Expand Down
5 changes: 5 additions & 0 deletions src/components/teaching/teaching-today.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { focusRing } from "@/components/card-recipes";
import { InformationPageShell } from "@/components/information-page-shell";
import { ModeBandAction } from "@/components/mode-band/mode-band";
import { WorkGlassButton, WorkMiniRing, WorkRing, useWorkUndoToast } from "@/components/mode-kit/work";
import { NewWorkModeOnly } from "@/components/work-mode-launch/work-mode-launch-provider";
import { WorkSetupRow } from "@/components/work-setup/work-setup-row";
import { ModeModuleSkeleton } from "@/components/mode-kit/module-skeleton";
import { LogToCpdSheet } from "@/components/teaching/log-to-cpd-sheet";
import { recordChart } from "@/components/teaching/my-record-model";
Expand Down Expand Up @@ -129,6 +131,9 @@ function TodayScreen({ demoMode }: { demoMode: boolean }) {
<InformationPageShell width="narrow" gap={false} testId="teaching-today">
<T5Page>
<h1 className="sr-only">Today</h1>
<NewWorkModeOnly>
<WorkSetupRow />
</NewWorkModeOnly>
{now && today ? (
<TodayBody
view={view}
Expand Down
Loading
Loading