Skip to content

#252 Show Application Stats On Position Cards For Admins And Managers#259

Merged
b-at-neu merged 12 commits into
devfrom
252-show-application-stats-on-position-cards-for-admins-and-managers
Jun 29, 2026
Merged

#252 Show Application Stats On Position Cards For Admins And Managers#259
b-at-neu merged 12 commits into
devfrom
252-show-application-stats-on-position-cards-for-admins-and-managers

Conversation

@b-at-neu

Copy link
Copy Markdown
Collaborator

Closes #252

Summary

  • Adds a compact application stats cluster to position cards for admins and managers, showing Total plus Applied / Interview Scheduled / Accepted / Rejected counts inline
  • Adds getPositionApplicationStats() data function using a single groupBy query (no N+1) and exposes it only to cards the caller manages
  • Zero-count tiles are dimmed rather than hidden, keeping the cluster shape stable; stats stack below the description on mobile

Changes

  • lib/types.ts — added PositionApplicationStats type (positionId, counts, total)
  • lib/constants.ts — added POSITION_CARD_STAT_STATUSES tuple (the 4 surfaced statuses in order)
  • prisma/data/applications.ts — added getPositionApplicationStats(positionIds): early-returns an empty Map when the list is empty, otherwise issues a single groupBy(['positionId', 'status']) query filtered to non-draft/non-withdrawn; folds rows into a Map<string, PositionApplicationStats>; ensures every requested position has an entry
  • components/features/position-card.tsx — added optional applicationStats prop; restructured description region into left (description/date) + right (stats cluster) row with flex-col sm:flex-row; added co-located PositionStatCluster server sub-component with Total lead tile + 2×2 status grid using design-token dot colors from STATUS_BADGE_VARIANT_TO_DOT
  • app/(main)/positions/page.tsx — fetches stats for managed position IDs after positions resolve; passes applicationStats only to cards where canManage is true

Testing plan

  • As an admin, open /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>)
  • As a manager (non-admin), open /positions: only cards for positions you manage show the cluster; non-managed open positions show none
  • As an applicant / anonymous user: no stats cluster on any card; description spans full width unchanged
  • Seed a position with applications across statuses including some draft and withdrawn: confirm draft/withdrawn are excluded from both Total and the per-status tiles
  • A managed position with zero submitted applications shows Total 0 with all-dimmed status tiles (not a blank/missing cluster)
  • Resize to mobile width (375px): stats stack below the description, tiles remain readable, no overflow or layout shift; description still truncates to two lines
  • Confirm a single DB round-trip for stats (one groupBy query, not one per card) — check server logs or query count

Automated checks

  • npm run prettier:check — passed
  • npm run eslint:check — passed (0 warnings)
  • npm run tsc:check — passed

Notes

  • getPositionApplicationStats does not authenticate itself — it is a cross-user aggregate read that the page scopes to managedIds (admin = all positions, manager = assigned positions). A // must be called with caller-managed position IDs only comment is included matching the convention used by getApplicationStatusCounts.
  • PositionStatCluster is kept as a co-located private sub-component (not exported) because it is only meaningful within PositionCard. If a second consumer appears, it should be promoted to components/features/.
  • Dot colors are resolved via APPLICATION_STATUS_BADGE_VARIANT[status]STATUS_BADGE_VARIANT_TO_DOT[variant], the same chain used by PipelineSummary and StatCard.

@b-at-neu b-at-neu self-assigned this Jun 27, 2026
@vercel

vercel Bot commented Jun 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
aplio Ready Ready Preview, Comment Jun 29, 2026 9:00am

@b-at-neu b-at-neu added the ready for review PR ready for review agent label Jun 27, 2026
@b-at-neu b-at-neu added reviewing Review agent working (in-flight) and removed ready for review PR ready for review agent labels Jun 27, 2026

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Cycle 1 · needs revision

1 open — 1 🟠 Medium (see inline)

Comment thread components/features/position-card.tsx Outdated
@b-at-neu b-at-neu added needs revision Review found issues that need fixing revising Revise agent working (in-flight) and removed reviewing Review agent working (in-flight) needs revision Review found issues that need fixing labels Jun 27, 2026
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 1

fixed R1-M1 · affa0cf

@b-at-neu b-at-neu added ready for review PR ready for review agent reviewing Review agent working (in-flight) and removed revising Revise agent working (in-flight) ready for review PR ready for review agent labels Jun 27, 2026

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Cycle 2 · approved

0 open — all prior findings resolved; no new issues found

@b-at-neu b-at-neu added approved Review passed, ready to merge and removed reviewing Review agent working (in-flight) labels Jun 27, 2026

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Move the status badge ("Open", "Draft", etc.) inline with the position title. Place it directly to the right of the <h3> title — e.g. a flex items-center gap-2 row containing [title] [status badge]. Remove it from wherever it currently sits as a standalone element.

  2. Place PositionStatCluster on the far right of the bottom row, with action buttons directly to its left. The bottom of the card should be a single flex 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.

  3. 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.

@b-at-neu b-at-neu added needs revision Review found issues that need fixing revising Revise agent working (in-flight) and removed approved Review passed, ready to merge needs revision Review found issues that need fixing labels Jun 27, 2026
@b-at-neu
b-at-neu force-pushed the 252-show-application-stats-on-position-cards-for-admins-and-managers branch from affa0cf to 127b354 Compare June 27, 2026 09:54
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 3

fixed R3-C1 · 127b354

@b-at-neu b-at-neu added ready for review PR ready for review agent reviewing Review agent working (in-flight) and removed revising Revise agent working (in-flight) labels Jun 27, 2026
@b-at-neu b-at-neu added the approved Review passed, ready to merge label Jun 27, 2026
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Code Review

Cycle 9 — 1 Medium finding


R9-M1 · Medium — Two-column view: badge should be at the far right of the title row

In the two-column card layout (when applicationStats is present), the PositionStatusBadge should be pushed to the far right of the CardHeader title row — not sitting immediately after the title text.

Fix in components/features/position-card.tsx — the title row in CardHeader:

When applicationStats is present, change the title row to use justify-between:

<div className={cn(
  "flex items-center gap-2",
  applicationStats && "justify-between"
)}>
  <CardTitle ...>{position.title}</CardTitle>
  <PositionStatusBadge ... />
</div>

The one-column view (no applicationStats) must stay exactly as-is — badge immediately adjacent to the title, no justify-between.

@b-at-neu b-at-neu removed the approved Review passed, ready to merge label Jun 27, 2026
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 8

fixed R8-N1 · 025e680

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Cycle 9 · approved

0 open — clean

@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Code Review

Cycle 10 — 1 Medium finding


R10-M1 · Medium — Two-column view: badge must be at the far right of the title row

The PositionStatusBadge still sits immediately adjacent to the title text in both layouts. In the two-column view (when applicationStats is present), the badge must be pushed to the far right of the title row.

Fix in components/features/position-card.tsx — find the <div> wrapping <CardTitle> and <PositionStatusBadge> in CardHeader and change it to:

<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 applicationStats is undefined (one-column view), the justify-between class is NOT added — badge stays adjacent to the title. When applicationStats is present (two-column view), justify-between pushes the badge to the far right. Do not change anything else in the card.

b-at-neu and others added 8 commits June 27, 2026 17:27
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>
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 10

fixed R10-M1 · 1b4d4d9

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Code Review

Cycle 11 — 1 Medium finding


R11-M1 · Medium — Badge conditional is inverted

The justify-between condition is wrong. It should be present on the one-column view (no applicationStats) and absent on the two-column view (with applicationStats).

Fix in components/features/position-card.tsx — the title-row <div> in CardHeader:

<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>
  • !applicationStats → one-column view → justify-between → badge pushed to the far right ✓
  • applicationStats present → two-column view → no justify-between → badge stays adjacent to title ✓

Also restore CardTitle to className="text-lg leading-snug" (the text-base font-semibold change from Cycle 10 was unintentional).

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>
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 10

fixed R10-L1, R10-L2 · b1f2dc3

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Cycle 11 · approved

0 open — R10-L1 and R10-L2 resolved; CI passes; no new findings

@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Code Review

Cycle 12 — 1 Medium finding


R12-M1 · Medium — Stat cluster must align with the top of the card, not just CardContent

The stat cluster currently lives inside CardContent alongside the body text and buttons. This means it sits below CardHeader, not at the card's top edge. The stat cluster must be a sibling of the entire left column (header + content), so it reaches the card's top.

Restructure components/features/position-card.tsx — wrap the card's inner content in a top-level flex row when applicationStats is present, with CardHeader and CardContent both inside the left column:

<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:

  • The outer <div> becomes sm:flex sm:flex-row only when applicationStats is present — no layout change for one-column view.
  • CardHeader and CardContent both live in the left flex column so the title/header is left-only.
  • The right column <div> is hidden on mobile (stats only show in wide layout), sm:flex sm:items-start to top-align the cluster.
  • sm:pl-0 on the right column removes left padding so the cluster sits flush against the left column's right edge; keep sm:p-6 for top/right/bottom padding to match the card's inner spacing.
  • The !applicationStats && "justify-between" on the title row stays — one-column view badge remains right-aligned.
  • Do not change PositionStatCluster itself.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 12

fixed R12-M1 · 0b74bf4

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Cycle 12 · needs revision

1 open — 1 🟠 Medium (see inline)

Comment thread components/features/position-card.tsx Outdated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 12

fixed R12-M1 · 183ff67

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Cycle 13 · needs revision

1 open — 1 🟠 Medium (see inline)

Comment thread components/features/position-card.tsx Outdated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 13

fixed R13-M1 · 847e8d9

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Cycle 14 · approved

0 open — R13-M1 resolved; CI passes; no new findings

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Review passed, ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Show Application Stats On Position Cards For Admins And Managers

1 participant