- Add viewer role to backend enum + Alembic migration - SSO auto-provisioned users now get viewer (lowest privilege) by default - Wire admin/users page to real API (replace mock data), with add/edit/deactivate - Fix frontend UserRole enum to match backend (TM_MANAGER, REVIEWER) - Replace hardcoded mock user in Sidebar with real auth, filter admin-only nav items, wire logout - Add seed script to set default admins (daveporter, vadymsamoilenko) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
112 lines
3.7 KiB
TypeScript
112 lines
3.7 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card } from "@/components/ui/card";
|
|
import type { User } from "@/lib/types";
|
|
import { formatDate, getInitials } from "@/lib/utils";
|
|
import { getRoleDisplayName } from "@/lib/auth";
|
|
import { Pencil, MoreHorizontal } from "lucide-react";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
DropdownMenuSeparator,
|
|
} from "@/components/ui/dropdown-menu";
|
|
|
|
const roleVariant: Record<string, "green" | "blue" | "amber" | "gray" | "default"> = {
|
|
ADMIN: "default",
|
|
TM_MANAGER: "blue",
|
|
REVIEWER: "green",
|
|
VIEWER: "gray",
|
|
};
|
|
|
|
interface UserTableProps {
|
|
users: User[];
|
|
onEdit: (user: User) => void;
|
|
onToggleActive: (user: User) => void;
|
|
}
|
|
|
|
export function UserTable({ users, onEdit, onToggleActive }: UserTableProps) {
|
|
return (
|
|
<Card>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>User</TableHead>
|
|
<TableHead>Role</TableHead>
|
|
<TableHead>Status</TableHead>
|
|
<TableHead>Last Login</TableHead>
|
|
<TableHead>Created</TableHead>
|
|
<TableHead className="w-[80px]"></TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{users.map((user) => (
|
|
<TableRow key={user.id}>
|
|
<TableCell>
|
|
<div className="flex items-center gap-3">
|
|
<div className="h-8 w-8 rounded-full bg-amazon-dark flex items-center justify-center text-white text-xs font-bold">
|
|
{getInitials(user.name)}
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-medium">{user.name}</p>
|
|
<p className="text-xs text-gray-500">{user.email}</p>
|
|
</div>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Badge variant={roleVariant[user.role] || "gray"}>
|
|
{getRoleDisplayName(user.role)}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Badge variant={user.is_active ? "green" : "gray"}>
|
|
{user.is_active ? "Active" : "Inactive"}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell className="text-sm text-gray-500">
|
|
{user.last_login ? formatDate(user.last_login) : "Never"}
|
|
</TableCell>
|
|
<TableCell className="text-sm text-gray-500">
|
|
{formatDate(user.created_at)}
|
|
</TableCell>
|
|
<TableCell>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" size="icon" className="h-8 w-8">
|
|
<MoreHorizontal className="h-4 w-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuItem onClick={() => onEdit(user)}>
|
|
<Pencil className="h-3.5 w-3.5 mr-2" />
|
|
Edit
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem
|
|
className="text-amazon-error"
|
|
onClick={() => onToggleActive(user)}
|
|
>
|
|
{user.is_active ? "Deactivate" : "Activate"}
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</Card>
|
|
);
|
|
}
|