The primary action is now "Run daily review": no form, no question. It starts the new INIT door dni-act-review, which is the same activity a scheduler performs at 6am; the button exists so a room does not have to wait until 6am. Ava sweeps every payer, department and front desk against a materiality bar and, if nothing clears it, records "nothing to report" and closes the case herself — it never reaches a desk. "Ask a question" stays, demoted to a ghost button, for the follow-up path. A second button replays a quiet morning: the demo data is static, so every live sweep would flag the same August spike. Anchoring one run on 2026-07-26 lets it genuinely come back empty, and the contrast — one quiet morning beside one $1.09M morning — is the pitch. Queue cards and the case page now carry the sweep outcome: what she reviewed, how many patterns she flagged, and what kind (payer rule change, front desk, expiring appeals, …). A closed quiet case reads "Nothing to report" rather than "Closed", because those are different outcomes. Backend counterpart: sm2/custom-apps/denials-investigator/08_daily_review.sql and the sweep-first charter in 05_ai_employee.sql. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
106 lines
4.1 KiB
TypeScript
106 lines
4.1 KiB
TypeScript
import { NavLink, Outlet, useNavigate } from 'react-router-dom';
|
|
import { LogOut, Radar, Search } from 'lucide-react';
|
|
import { useZino } from '../api/provider';
|
|
import { QUEUES, prettyRole } from '../api/config';
|
|
import { NorthlakeLockup } from '../components/NorthlakeLogo';
|
|
import { ThemeToggle } from '../components/ThemeToggle';
|
|
import { Button } from '../components/core';
|
|
|
|
/**
|
|
* The chrome.
|
|
*
|
|
* The sidebar IS the pipeline: every entry is the record view filtered on
|
|
* current_state_name, in the order a case actually moves. A person learns the
|
|
* process by using the app. Awaiting Review is first because it is the only
|
|
* queue that needs a human, and the one this console exists to serve.
|
|
*
|
|
* Nothing here is role-gated. Hiding a queue protects nothing — the workflow
|
|
* re-checks RBAC on every submission — and hiding one from the person who needs
|
|
* it is a worse failure than showing a button that comes back "not permitted".
|
|
*/
|
|
export function Shell() {
|
|
const { user, logout } = useZino();
|
|
const navigate = useNavigate();
|
|
|
|
return (
|
|
<div className="flex min-h-full flex-col">
|
|
<header className="sticky top-0 z-30 bg-panel">
|
|
<div className="flex items-center gap-4 px-4 py-2.5">
|
|
<NorthlakeLockup />
|
|
|
|
<div className="ml-auto flex items-center gap-3">
|
|
{/* The sweep is the product; the question is the exception. */}
|
|
<Button kind="primary" onClick={() => navigate('/review')}>
|
|
<Radar className="h-4 w-4" />
|
|
Run daily review
|
|
</Button>
|
|
<Button kind="ghost" onClick={() => navigate('/new')} title="Ask Ava a specific question">
|
|
<Search className="h-4 w-4" />
|
|
Ask a question
|
|
</Button>
|
|
|
|
{user && (
|
|
<div className="hidden text-right sm:block">
|
|
<div className="text-[13px] leading-tight font-semibold text-ink">
|
|
{user.name || user.email}
|
|
</div>
|
|
<div className="text-[11px] leading-tight text-faint">
|
|
{(user.roles ?? []).map(prettyRole).join(' · ') || 'No role'}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<ThemeToggle />
|
|
|
|
<button
|
|
onClick={logout}
|
|
title="Sign out"
|
|
aria-label="Sign out"
|
|
className="rounded-md p-2 text-muted transition-colors hover:bg-panel2 hover:text-ink focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand"
|
|
>
|
|
<LogOut className="h-4 w-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div aria-hidden="true" className="h-[3px] bg-brand" />
|
|
</header>
|
|
|
|
<div className="flex flex-1 flex-col lg:flex-row">
|
|
<nav className="shrink-0 border-b border-line bg-panel lg:w-60 lg:border-r lg:border-b-0">
|
|
<ul className="flex gap-1 overflow-x-auto p-2 lg:flex-col lg:overflow-visible">
|
|
{QUEUES.map((q) => (
|
|
<li key={q.path} className="shrink-0 lg:shrink">
|
|
<NavLink
|
|
to={`/q/${q.path}`}
|
|
className={({ isActive }) =>
|
|
`block rounded-md px-3 py-2 transition-colors ${
|
|
isActive ? 'bg-brand text-brand-fg' : 'text-muted hover:bg-panel2 hover:text-ink'
|
|
}`
|
|
}
|
|
>
|
|
{({ isActive }) => (
|
|
<>
|
|
<span className="block text-[13px] font-semibold whitespace-nowrap">
|
|
{q.label}
|
|
</span>
|
|
<span
|
|
className={`hidden text-[11px] lg:block ${isActive ? 'text-brand-fg/70' : 'text-faint'}`}
|
|
>
|
|
{q.hint}
|
|
</span>
|
|
</>
|
|
)}
|
|
</NavLink>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</nav>
|
|
|
|
<main className="min-w-0 flex-1 p-4 lg:p-6">
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|