feat: fix project status counts, add top pagination and sortable columns
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m39s
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m39s
- Status counts now show each project's latest round state only (no more inflated counts from projects passing multiple rounds) - Add pagination controls at top of projects, members, and observer lists - Add sortable column headers to admin projects table (title, category, program, assignments, status) and members table (name, role, status, last login) - Backend: add sortBy/sortDir params to project.list and user.list Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,7 @@ import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { UserAvatar } from '@/components/shared/user-avatar'
|
||||
import { UserActions, UserMobileActions } from '@/components/admin/user-actions'
|
||||
import { Pagination } from '@/components/shared/pagination'
|
||||
import { SortableHeader } from '@/components/shared/sortable-header'
|
||||
import { Plus, Users, Search, Mail, Loader2, X, Send } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { formatRelativeTime } from '@/lib/utils'
|
||||
@@ -138,6 +139,18 @@ export function MembersContent() {
|
||||
const roles = TAB_ROLES[tab]
|
||||
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
|
||||
const [sortBy, setSortBy] = useState<string | undefined>(undefined)
|
||||
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc')
|
||||
|
||||
const handleSort = (column: string) => {
|
||||
if (sortBy === column) {
|
||||
setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'))
|
||||
} else {
|
||||
setSortBy(column)
|
||||
setSortDir('asc')
|
||||
}
|
||||
updateParams({ page: '1' })
|
||||
}
|
||||
|
||||
const { data: currentUser } = trpc.user.me.useQuery()
|
||||
const currentUserRole = currentUser?.role as RoleValue | undefined
|
||||
@@ -147,6 +160,8 @@ export function MembersContent() {
|
||||
search: search || undefined,
|
||||
page,
|
||||
perPage: 20,
|
||||
sortBy: sortBy as 'name' | 'email' | 'role' | 'status' | 'lastLoginAt' | 'createdAt' | undefined,
|
||||
sortDir: sortBy ? sortDir : undefined,
|
||||
})
|
||||
|
||||
const invitableIdsQuery = trpc.user.listInvitableIds.useQuery(
|
||||
@@ -290,6 +305,17 @@ export function MembersContent() {
|
||||
<MembersSkeleton />
|
||||
) : data && data.users.length > 0 ? (
|
||||
<>
|
||||
{/* Top Pagination */}
|
||||
{data.totalPages > 1 && (
|
||||
<Pagination
|
||||
page={page}
|
||||
totalPages={data.totalPages}
|
||||
total={data.total}
|
||||
perPage={data.perPage}
|
||||
onPageChange={(newPage) => updateParams({ page: String(newPage) })}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Bulk selection controls */}
|
||||
<Card>
|
||||
<CardContent className="py-3 flex flex-wrap items-center justify-between gap-2">
|
||||
@@ -334,12 +360,12 @@ export function MembersContent() {
|
||||
/>
|
||||
)}
|
||||
</TableHead>
|
||||
<TableHead>Member</TableHead>
|
||||
<TableHead>Role</TableHead>
|
||||
<SortableHeader label="Member" column="name" currentSort={sortBy} currentDir={sortDir} onSort={handleSort} />
|
||||
<SortableHeader label="Role" column="role" currentSort={sortBy} currentDir={sortDir} onSort={handleSort} />
|
||||
<TableHead>Expertise</TableHead>
|
||||
<TableHead>Assignments</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Last Login</TableHead>
|
||||
<SortableHeader label="Status" column="status" currentSort={sortBy} currentDir={sortDir} onSort={handleSort} />
|
||||
<SortableHeader label="Last Login" column="lastLoginAt" currentSort={sortBy} currentDir={sortDir} onSort={handleSort} />
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -681,6 +707,17 @@ function ApplicantsTabContent({ search, searchInput, setSearchInput }: { search:
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Top Pagination */}
|
||||
{data.totalPages > 1 && (
|
||||
<Pagination
|
||||
page={page}
|
||||
totalPages={data.totalPages}
|
||||
total={data.total}
|
||||
perPage={data.perPage}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Desktop table */}
|
||||
<Card className="hidden md:block">
|
||||
<Table>
|
||||
|
||||
Reference in New Issue
Block a user