fix(setup-queue): surface dead-policy clients hidden behind badge count

The Manager nav badge, dashboard tile, and metrics gauge counted Shape/
Shape 2 clients missing an advocate/setup with ANY policy, while the
Setup Queue page itself required an ACTIVE (non-dead-status) policy.
Clients with only Cancelled/Expired/etc policies inflated the badge but
never appeared anywhere except a bare "(N skipped)" note with no names.

- setup/page.tsx: replace the bare skipped count with a full "No Active
  Policies" card listing each client (linked), policy count, what's
  missing, and a Mark N/A action
- Align the active-policy filter across setup-queue API, manager
  dashboard count, and the Prometheus setup-queue gauge so all four
  surfaces agree on what's actually actionable
This commit is contained in:
lorentz 2026-07-17 03:19:40 +00:00
parent 01ecfacd04
commit 6a5006822b
4 changed files with 55 additions and 9 deletions

View file

@ -26,7 +26,7 @@ export default async function ManagerPage() {
prisma.client.count({
where: {
designation: { name: { in: ['Shape', 'Shape 2'] } },
policies: { some: {} },
policies: { some: { status: { notIn: ['Cancelled', 'Expired', 'Non-Renewed', 'Rewritten', 'Not taken'] } } },
setupNaAt: null,
OR: [
{ claimsAdvocateId: null },

View file

@ -34,7 +34,7 @@ export default async function SetupQueuePage() {
const DEAD_STATUSES = ['Cancelled', 'Expired', 'Non-Renewed', 'Rewritten', 'Not taken']
const [clients, skippedCount, naClients] = await Promise.all([
const [clients, skippedClients, naClients] = await Promise.all([
prisma.client.findMany({
where: {
designation: { name: { in: ['Shape', 'Shape 2'] } },
@ -59,13 +59,15 @@ export default async function SetupQueuePage() {
},
orderBy: { createdAt: 'asc' },
}),
prisma.client.count({
prisma.client.findMany({
where: {
designation: { name: { in: ['Shape', 'Shape 2'] } },
setupNaAt: null,
OR: [{ claimsAdvocateId: null }, { setupCompletedAt: null }],
NOT: { policies: { some: { status: { notIn: DEAD_STATUSES } } } },
},
select: { id: true, name: true, claimsAdvocateId: true, setupCompletedAt: true, _count: { select: { policies: true } } },
orderBy: { name: 'asc' },
}),
prisma.client.findMany({
where: {
@ -110,11 +112,6 @@ export default async function SetupQueuePage() {
<h1 className="text-3xl font-bold">New Client Setup</h1>
<p className="text-muted-foreground mt-1">
Clients that need a claims advocate or renewal group configuration.
{skippedCount > 0 && (
<span className="ml-2 text-xs text-muted-foreground/70">
({skippedCount} skipped no active policies)
</span>
)}
</p>
</div>
<Badge variant="secondary" className="text-base px-3 py-1">
@ -216,6 +213,52 @@ export default async function SetupQueuePage() {
</Card>
)}
{skippedClients.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="text-base">No Active Policies ({skippedClients.length})</CardTitle>
<CardDescription>
Shape/Shape 2 clients missing an advocate or setup completion, but with no
active (non-dead) policies likely lost business or a data cleanup issue.
Not shown in the queue above since there&apos;s nothing to configure yet.
Mark N/A once confirmed, or assign an advocate if the client is still active.
</CardDescription>
</CardHeader>
<CardContent className="p-0">
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead className="border-b bg-muted/40">
<tr>
<th className="px-4 py-3 text-left font-medium text-muted-foreground">Client</th>
<th className="px-4 py-3 text-center font-medium text-muted-foreground">Total Policies</th>
<th className="px-4 py-3 text-left font-medium text-muted-foreground">Missing</th>
<th className="px-4 py-3" />
</tr>
</thead>
<tbody className="divide-y">
{skippedClients.map((c) => (
<tr key={c.id} className="hover:bg-muted/30 transition-colors">
<td className="px-4 py-3 font-medium">
<Link href={`/clients/${c.id}`} className="hover:underline text-primary">
{c.name}
</Link>
</td>
<td className="px-4 py-3 text-center text-muted-foreground">{c._count.policies}</td>
<td className="px-4 py-3 text-muted-foreground">
{[!c.claimsAdvocateId && 'Advocate', !c.setupCompletedAt && 'Setup'].filter(Boolean).join(', ')}
</td>
<td className="px-4 py-3 text-right">
<SetupNaButton clientId={c.id} clientName={c.name} />
</td>
</tr>
))}
</tbody>
</table>
</div>
</CardContent>
</Card>
)}
{naClients.length > 0 && (
<Card>
<CardHeader>

View file

@ -18,9 +18,11 @@ export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const countOnly = searchParams.get('count') === 'true'
const DEAD_STATUSES = ['Cancelled', 'Expired', 'Non-Renewed', 'Rewritten', 'Not taken']
const where = {
designation: { name: { in: ['Shape', 'Shape 2'] } },
policies: { some: {} },
policies: { some: { status: { notIn: DEAD_STATUSES } } },
setupNaAt: null,
OR: [
{ claimsAdvocateId: null },

View file

@ -74,6 +74,7 @@ export async function GET(request: NextRequest) {
prisma.client.count({
where: {
designation: { name: { in: ['Shape', 'Shape 2'] } },
policies: { some: { status: { notIn: ['Cancelled', 'Expired', 'Non-Renewed', 'Rewritten', 'Not taken'] } } },
setupNaAt: null,
OR: [{ claimsAdvocateId: null }, { setupCompletedAt: null }],
},