Feature/66 admin dashboard users tab#67
Conversation
There was a problem hiding this comment.
Pull request overview
Implements the Admin Users dashboard route with server-side user data loading and a client-side table for filtering, sorting, and pagination.
Changes:
- Adds
/userspage structure, bounded layout, profile role helpers, and role query. - Adds users table components with name/status/role filtering, alphabetical sorting, dynamic page sizing, and action icons.
- Updates shared authenticated layout and generic data table pagination styling/behavior.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
components/data-table.tsx |
Updates generic table footer to show page counts and numbered pagination. |
app/(authenticated)/users/queries.ts |
Adds query for distinct profile roles. |
app/(authenticated)/users/profile-role-label.ts |
Adds role label mapping and shared UserRow type. |
app/(authenticated)/users/page.tsx |
Adds server-side user fetching and renders the users dashboard. |
app/(authenticated)/users/layout.tsx |
Adds bounded-height layout for the users route. |
app/(authenticated)/users/_components/users-data-table.tsx |
Adds dynamic paginated users table with resize-based page sizing. |
app/(authenticated)/users/_components/users-columns.tsx |
Defines users table columns and row actions. |
app/(authenticated)/users/_components/users-client.tsx |
Adds client-side filters, tabs, search, sort, and table wiring. |
app/(authenticated)/layout.tsx |
Adjusts authenticated layout sizing/overflow for nested dashboard content. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| async function UsersContent() { | ||
| const [users, distinctRoles] = await Promise.all([ | ||
| fetchUsers(), | ||
| listDistinctProfileRoles(), | ||
| ]); |
| const fullName = `${u.firstName} ${u.lastName}`; | ||
| const email = `${u.firstName.toLowerCase()}.${u.lastName.toLowerCase()}@mcld.ca`; |
| id: "createdAt", | ||
| header: "Last Login", | ||
| meta: { colWidth: "18%" }, | ||
| cell: ({ row }) => ( | ||
| <span className="block min-w-0 truncate text-sm text-muted-foreground"> | ||
| {new Intl.DateTimeFormat("en-CA", { | ||
| year: "numeric", | ||
| month: "short", | ||
| day: "numeric", | ||
| }).format(new Date(row.original.createdAt))} |
… functional button to add users in the dashboard
RenaudBernier
left a comment
There was a problem hiding this comment.
- Add loader for when the table is loading
- Remove the Add user button, you can add it in your next issue
…d remove unused button
…g indication in UsersPage
done |
martin0024
left a comment
There was a problem hiding this comment.
Good job, nothing to say really. Looks good.
Only thing is why everyone is Last Login is 16 of May, check it works.
| function AvatarCircle({ name }: { name: string }) { | ||
| const [first = "", last = ""] = name.trim().split(" "); | ||
| const initials = `${first[0] ?? ""}${last[0] ?? ""}`.toUpperCase(); | ||
| return ( | ||
| <span className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-semibold text-muted-foreground"> | ||
| {initials} | ||
| </span> | ||
| ); |
There was a problem hiding this comment.
Please use avatar component: npx shadcn@latest add avatar
There was a problem hiding this comment.
Please use avatar component:
npx shadcn@latest add avatar
done
| return ( | ||
| <span className="inline-flex items-center gap-1.5 text-sm"> | ||
| <span | ||
| className={`h-2 w-2 rounded-full ${ | ||
| active ? "bg-green-500" : "bg-muted-foreground/50" | ||
| }`} | ||
| /> | ||
| <span | ||
| className={ | ||
| active ? "text-foreground" : "text-muted-foreground" | ||
| } | ||
| > | ||
| {active ? "Active" : "Inactive"} | ||
| </span> | ||
| </span> | ||
| ); |
There was a problem hiding this comment.
| return ( | |
| <span className="inline-flex items-center gap-1.5 text-sm"> | |
| <span | |
| className={`h-2 w-2 rounded-full ${ | |
| active ? "bg-green-500" : "bg-muted-foreground/50" | |
| }`} | |
| /> | |
| <span | |
| className={ | |
| active ? "text-foreground" : "text-muted-foreground" | |
| } | |
| > | |
| {active ? "Active" : "Inactive"} | |
| </span> | |
| </span> | |
| ); | |
| <Badge variant={active ? "default" : "secondary"} className="gap-1.5"> | |
| <span | |
| className={`h-2 w-2 rounded-full ${ | |
| active ? "bg-green-500" : "bg-muted-foreground/50" | |
| }`} | |
| /> | |
| {active ? "Active" : "Inactive"} | |
| </Badge> |
There was a problem hiding this comment.
utils/supabase/middleware.ts gates /dashboard/* on ROLES.ADMIN but /users is unprotected, so any signed-in user can access the page.
| cell: ({ row }) => { | ||
| const u = row.original; | ||
| const fullName = `${u.firstName} ${u.lastName}`; | ||
| const email = `${u.firstName.toLowerCase()}.${u.lastName.toLowerCase()}@mcld.ca`; |
There was a problem hiding this comment.
why is there fake email rendered in the table?
There was a problem hiding this comment.
why is there fake email rendered in the table?
fixed
| @@ -0,0 +1,18 @@ | |||
| import { LoaderIcon } from "lucide-react"; | |||
There was a problem hiding this comment.
| import { LoaderIcon } from "lucide-react"; | |
| import { Loader2Icon } from "lucide-react"; |
| function Spinner({ className, ...props }: React.ComponentProps<"svg">) { | ||
| return ( | ||
| <div className="flex flex-1 items-center justify-center"> | ||
| <LoaderIcon |
There was a problem hiding this comment.
| <LoaderIcon | |
| <Loader2Icon |
… loading animation
…o include emails, and enhanced search functionality to filter by email
…tatus indicator with Badge for improved UI consistency
Closes #66
Overview
Implemented the fully functional Admin Users Dashboard. This includes a new
/usersroute with server-side data fetching and a dynamic client-side interface for managing user records.Key Changes:
UsersDataTablewhich uses aResizeObserverto automatically calculate the optimal number of rows to display based on available vertical screen space.UsersClientto manage status tabs (All, Active, Inactive), a role-based filter dropdown, and a real-time name search.Testing
Manual Testing:
pageSizewhen the browser window is resized.Checklist
Notes
UsersDataTablerelies on the parent container having a bounded height (implemented inusers/layout.tsx).