#252 Show Application Stats On Position Cards For Admins And Managers#259
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 1 · needs revision
1 open — 1 🟠 Medium (see inline)
Revision — Cycle 1fixed R1-M1 · affa0cf |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 2 · approved
0 open — all prior findings resolved; no new issues found
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Layout Change Required
Cycle 3 — 1 Critical finding
R3-C1 · Critical — Stat cluster placement makes cards significantly taller; layout must be revised
The current implementation inserts the PositionStatCluster between the description and the action buttons, adding height to every managed card. The correct layout keeps the card height unchanged by placing stats on the same row as the buttons.
Required layout changes in components/features/position-card.tsx:
-
Move the status badge ("Open", "Draft", etc.) inline with the position title. Place it directly to the right of the
<h3>title — e.g. aflex items-center gap-2row containing[title] [status badge]. Remove it from wherever it currently sits as a standalone element. -
Place
PositionStatClusteron the far right of the bottom row, with action buttons directly to its left. The bottom of the card should be a singleflex items-center justify-between(or similar) row:[action buttons] ...space... [PositionStatCluster]The stat cluster must not appear below the buttons — it must sit beside them on the same row.
-
Do not change card height. The stat cluster occupies horizontal space that is currently empty (the right side), not vertical space below existing content.
The description and other card content above the bottom row should be unchanged.
affa0cf to
127b354
Compare
Revision — Cycle 3fixed R3-C1 · 127b354 |
Code ReviewCycle 9 — 1 Medium finding R9-M1 · Medium — Two-column view: badge should be at the far right of the title rowIn the two-column card layout (when Fix in When <div className={cn(
"flex items-center gap-2",
applicationStats && "justify-between"
)}>
<CardTitle ...>{position.title}</CardTitle>
<PositionStatusBadge ... />
</div>The one-column view (no |
Revision — Cycle 8fixed R8-N1 · 025e680 |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 9 · approved
0 open — clean
Code ReviewCycle 10 — 1 Medium finding R10-M1 · Medium — Two-column view: badge must be at the far right of the title rowThe Fix in <div className={cn("flex items-center gap-2", applicationStats && "justify-between")}>
<CardTitle className="text-base font-semibold">{position.title}</CardTitle>
<PositionStatusBadge status={position.status} />
</div>When |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Place PositionStatCluster on the same row as action buttons (far right) rather than above them, keeping card height unchanged per R3-C1. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Restructure position card as two-column flex layout: left column holds title, description, and buttons (mt-auto to bottom); right column holds stat cluster with self-start so it anchors to the top-right regardless of description length. Also merge dev's three-section page structure with stats threaded through managed positions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
flex flex-col base so stats stack below description on narrow viewports; sm:flex-row restores side-by-side on wider screens. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Admin branch now fetches stats for all positions and passes them to each card. Open and recently-closed sections pass stats to managed cards (where canManage is true) so the stat cluster renders correctly for managers everywhere. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Revision — Cycle 10fixed R10-M1 · 1b4d4d9 |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 10 · approved
2 open — 2 🟡 Low (non-blocking at cycle 10; see notes)
R10-L1 🟡 Low — position-card.tsx 1b4d4d9: The CardTitle class changes from text-lg leading-snug to text-base font-semibold. This is an unplanned scope change that reduces the visual weight of the position title on all cards (managed and non-managed). text-base is one step smaller than text-lg. Consider reverting to text-lg or confirming this is intentional before merge.
R10-L2 🟡 Low — position-card.tsx 1b4d4d9: The header div conditional removes justify-between from non-managed cards (when applicationStats is absent). Before this PR, the status badge was always pushed to the far right of the header row via justify-between. After, non-managed cards (applicant/anonymous view) cluster the title and badge left with just gap-2. This is a visual regression on non-managed cards. Fix: apply justify-between unconditionally on the header row; keep the flex-col sm:flex-row two-column split only inside the CardContent region.
Code ReviewCycle 11 — 1 Medium finding R11-M1 · Medium — Badge conditional is invertedThe Fix in <div className={cn('flex items-center gap-2', !applicationStats && 'justify-between')}>
<CardTitle className="text-lg leading-snug">{position.title}</CardTitle>
<PositionStatusBadge status={position.status} />
</div>
Also restore |
Revert CardTitle to text-lg leading-snug (text-base font-semibold was unintentional). Fix the justify-between conditional: apply it when applicationStats is absent (one-column view), not when present. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Revision — Cycle 10fixed R10-L1, R10-L2 · b1f2dc3 |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 11 · approved
0 open — R10-L1 and R10-L2 resolved; CI passes; no new findings
Code ReviewCycle 12 — 1 Medium finding R12-M1 · Medium — Stat cluster must align with the top of the card, not just CardContentThe stat cluster currently lives inside Restructure <Card>
<div className={cn(applicationStats && "sm:flex sm:flex-row")}>
{/* Left column: header + content stacked */}
<div className={cn(applicationStats && "sm:flex sm:min-w-0 sm:flex-1 sm:flex-col")}>
<CardHeader className="pb-3">
<div className={cn("flex items-center gap-2", !applicationStats && "justify-between")}>
<CardTitle className="text-lg leading-snug">{position.title}</CardTitle>
<PositionStatusBadge status={position.status} />
</div>
{/* department / date metadata if any */}
</CardHeader>
<CardContent className={cn(applicationStats && "sm:flex sm:flex-1 sm:flex-col")}>
{/* existing body content */}
{/* buttons with mt-auto */}
</CardContent>
</div>
{/* Right column: stat cluster, top-aligned, only in two-column view */}
{applicationStats && (
<div className="hidden sm:flex sm:shrink-0 sm:items-start sm:p-6 sm:pl-0">
<PositionStatCluster stats={applicationStats} />
</div>
)}
</div>
</Card>Key points:
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Revision — Cycle 12fixed R12-M1 · 0b74bf4 |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 12 · needs revision
1 open — 1 🟠 Medium (see inline)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Revision — Cycle 12fixed R12-M1 · 183ff67 |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 13 · needs revision
1 open — 1 🟠 Medium (see inline)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Revision — Cycle 13fixed R13-M1 · 847e8d9 |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 14 · approved
0 open — R13-M1 resolved; CI passes; no new findings
Closes #252
Summary
getPositionApplicationStats()data function using a singlegroupByquery (no N+1) and exposes it only to cards the caller managesChanges
lib/types.ts— addedPositionApplicationStatstype (positionId,counts,total)lib/constants.ts— addedPOSITION_CARD_STAT_STATUSEStuple (the 4 surfaced statuses in order)prisma/data/applications.ts— addedgetPositionApplicationStats(positionIds): early-returns an empty Map when the list is empty, otherwise issues a singlegroupBy(['positionId', 'status'])query filtered to non-draft/non-withdrawn; folds rows into aMap<string, PositionApplicationStats>; ensures every requested position has an entrycomponents/features/position-card.tsx— added optionalapplicationStatsprop; restructured description region into left (description/date) + right (stats cluster) row withflex-col sm:flex-row; added co-locatedPositionStatClusterserver sub-component with Total lead tile + 2×2 status grid using design-token dot colors fromSTATUS_BADGE_VARIANT_TO_DOTapp/(main)/positions/page.tsx— fetches stats for managed position IDs after positions resolve; passesapplicationStatsonly to cards wherecanManageis trueTesting plan
/positions: every position card shows the stats cluster on the right; Total equals the sum of the four shown statuses (cross-check against/applications?positionId=<id>)/positions: only cards for positions you manage show the cluster; non-managed open positions show nonedraftandwithdrawn: confirm draft/withdrawn are excluded from both Total and the per-status tilesTotal 0with all-dimmed status tiles (not a blank/missing cluster)groupByquery, not one per card) — check server logs or query countAutomated checks
npm run prettier:check— passednpm run eslint:check— passed (0 warnings)npm run tsc:check— passedNotes
getPositionApplicationStatsdoes not authenticate itself — it is a cross-user aggregate read that the page scopes tomanagedIds(admin = all positions, manager = assigned positions). A// must be called with caller-managed position IDs onlycomment is included matching the convention used bygetApplicationStatusCounts.PositionStatClusteris kept as a co-located private sub-component (not exported) because it is only meaningful withinPositionCard. If a second consumer appears, it should be promoted tocomponents/features/.APPLICATION_STATUS_BADGE_VARIANT[status]→STATUS_BADGE_VARIANT_TO_DOT[variant], the same chain used byPipelineSummaryandStatCard.