Skip to content

Sort Open Positions By Closing Date Instead Of Alphabetically #627

Description

@b-at-neu

Problem

/positions lists open positions alphabetically:

export async function getOpenPositions(): Promise<PositionWithQuestions[]> {
  const positions = await prisma.position.findMany({
    where: { status: 'open', deletedAt: null },
    select: positionWithQuestionsSelect,
    orderBy: { title: 'asc' },        // ← alphabetical
  });
  return positions.filter((p) => isAcceptingApplications(p));
}

Alphabetical order carries no information an applicant needs. The position closing tomorrow is buried between two that close next month.

Wanted order

  1. Positions with a closesAt, soonest first.
  2. Positions without one, at the end, ordered by opensAt.
  3. Title as the final tiebreak.
orderBy: [
  { closesAt: { sort: 'asc', nulls: 'last' } },
  { opensAt: { sort: 'desc', nulls: 'last' } },
  { title: 'asc' },
]

Why this is safe here: getOpenPositions filters through isAcceptingApplications, so every row that survives is currently accepting — no past-closed rows and no upcoming ones. Ascending closesAt therefore cannot surface something that closed last week at the top, which is the obvious trap with this change. The .filter() runs after the query and Array.filter preserves order, so the database ordering carries through.

One thing to confirm

Which direction for opensAt? "Sort those at the end by the date that they opened on" doesn't say. I've written desc above — most recently opened first — on the grounds that for a position with no deadline the useful signal is recency, and asc would rank something opened six months ago above one posted yesterday. Flip it if you meant the literal ascending reading.

Non-goals

  • No change to getRecentlyClosedPositions, which feeds the separate recently-closed section of the same page and is correctly ordered closesAt desc — newest-closed first is right there.
  • No change to getOpenPositionsSummary (the dashboard widget) unless you want it consistent; it's a short list where alphabetical is defensible.
  • No user-facing sort control.
  • No change to isAcceptingApplications or getPositionAvailability.

Acceptance criteria

  • A position closing sooner appears above one closing later.
  • Positions with no closing date appear after every position that has one.
  • Among positions with no closing date, ordering follows opensAt in the agreed direction.
  • Two positions with identical dates fall back to title.
  • The recently-closed section is unchanged.
  • npm run prettier:check, eslint:check, tsc:check, test all pass.

Tests

  • tests/db/ — ordering across a fixture set covering: two close dates, a null close date, two nulls differing by opensAt, and an exact tie falling through to title.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions