import { useState } from "react"; import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Search, Plus, Download, Shield, Pencil, UserCog } from "lucide-react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { api } from "@/lib/api-client"; import { useToast } from "@/hooks/use-toast"; interface PlatformUser { id: number; name: string; email: string; login: string; user_type: string; active: boolean; phone: string; role_ids: number[]; roles: { id: number; name: string }[]; effective_permission_count: number; } interface RoleInfo { id: number; name: string; description: string; assigned: boolean; permission_count: number; } const TYPE_COLORS: Record = { admin: "bg-red-100 text-red-800 border-red-200", teacher: "bg-blue-100 text-blue-800 border-blue-200", student: "bg-green-100 text-green-800 border-green-200", user: "bg-gray-100 text-gray-800 border-gray-200", }; export default function UsersPage() { const [search, setSearch] = useState(""); const [typeFilter, setTypeFilter] = useState("all"); const [createOpen, setCreateOpen] = useState(false); const [editUser, setEditUser] = useState(null); const [rolesUser, setRolesUser] = useState(null); const [form, setForm] = useState({ name: "", email: "", password: "", phone: "" }); const { toast } = useToast(); const qc = useQueryClient(); const usersQ = useQuery({ queryKey: ["platform-users", search], queryFn: async () => { const params: Record = { size: 200 }; if (search) params.search = search; const res = await api.get<{ items?: PlatformUser[]; data?: PlatformUser[]; total: number }>("/users/list", params); return (res.items ?? res.data ?? []) as PlatformUser[]; }, }); const rolesQ = useQuery({ queryKey: ["user-roles-detail", rolesUser?.id], queryFn: async () => { if (!rolesUser) return null; return api.get<{ roles: RoleInfo[]; effective_permissions: unknown[] }>(`/users/${rolesUser.id}/roles`); }, enabled: !!rolesUser, }); const createMut = useMutation({ mutationFn: (data: { name: string; email: string; password: string; phone?: string }) => api.post<{ data: PlatformUser }>("/users/create", data), onSuccess: () => { qc.invalidateQueries({ queryKey: ["platform-users"] }); setCreateOpen(false); setForm({ name: "", email: "", password: "", phone: "" }); toast({ title: "User created" }); }, onError: (err: Error) => toast({ title: "Error", description: err.message, variant: "destructive" }), }); const updateMut = useMutation({ mutationFn: (data: { id: number; name?: string; email?: string; phone?: string }) => api.patch<{ data: PlatformUser }>("/users/update", data), onSuccess: () => { qc.invalidateQueries({ queryKey: ["platform-users"] }); setEditUser(null); toast({ title: "User updated" }); }, onError: (err: Error) => toast({ title: "Error", description: err.message, variant: "destructive" }), }); const toggleRoleMut = useMutation({ mutationFn: ({ userId, roleId }: { userId: number; roleId: number }) => api.post<{ assigned: boolean }>(`/users/${userId}/roles/toggle`, { role_id: roleId }), onSuccess: (res) => { qc.invalidateQueries({ queryKey: ["platform-users"] }); qc.invalidateQueries({ queryKey: ["user-roles-detail"] }); const verb = (res as { assigned: boolean }).assigned ? "assigned" : "removed"; toast({ title: `Role ${verb}` }); }, onError: (err: Error) => toast({ title: "Error", description: err.message, variant: "destructive" }), }); const users = usersQ.data ?? []; const filtered = users.filter((u) => { if (typeFilter !== "all" && u.user_type !== typeFilter) return false; return true; }); const typeCounts = { all: users.length, admin: users.filter((u) => u.user_type === "admin").length, teacher: users.filter((u) => u.user_type === "teacher").length, student: users.filter((u) => u.user_type === "student").length, }; function exportCsv() { const rows = [["Name", "Email", "Type", "Roles", "Permissions"]]; users.forEach((u) => rows.push([ u.name, u.email, u.user_type, u.roles.map((r) => r.name).join("; "), String(u.effective_permission_count), ]), ); const csv = rows.map((r) => r.map((c) => `"${c}"`).join(",")).join("\n"); const blob = new Blob([csv], { type: "text/csv" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "platform_users.csv"; a.click(); URL.revokeObjectURL(url); } const rolesData = rolesQ.data as { roles: RoleInfo[]; effective_permissions: unknown[] } | null; return (

Users

Manage all platform accounts — admins, teachers, and students.

setSearch(e.target.value)} />
All ({typeCounts.all}) Admins ({typeCounts.admin}) Teachers ({typeCounts.teacher}) Students ({typeCounts.student})
{usersQ.isLoading ? (
) : ( User Type Roles Permissions Status {filtered.length === 0 && ( No users found. )} {filtered.map((u) => { const initials = u.name .split(" ") .map((w) => w[0]) .join("") .slice(0, 2) .toUpperCase(); return (
{initials}

{u.name}

{u.email}

{u.user_type}
{u.roles.length > 0 ? u.roles.map((r) => ( {r.name} )) : No roles}
{u.effective_permission_count} {u.active ? "Active" : "Inactive"}
); })}
)} {/* Create User Dialog */} Create Platform User Creates an internal Odoo user (res.users) with login access. For students/teachers, use the LMS pages instead.
setForm((f) => ({ ...f, name: e.target.value }))} placeholder="e.g. John Admin" />
setForm((f) => ({ ...f, email: e.target.value }))} placeholder="admin@encoach.com" />
setForm((f) => ({ ...f, password: e.target.value }))} placeholder="Minimum 6 characters" />
setForm((f) => ({ ...f, phone: e.target.value }))} placeholder="+1234567890" />
{/* Edit User Dialog */} { if (!v) setEditUser(null); }}> Edit User Update profile for {editUser?.name} {editUser && (
setEditUser((u) => u ? { ...u, name: e.target.value } : null)} />
setEditUser((u) => u ? { ...u, email: e.target.value } : null)} />
setEditUser((u) => u ? { ...u, phone: e.target.value } : null)} />
)}
{/* Manage Roles Dialog */} { if (!v) setRolesUser(null); }}>
Manage Roles — {rolesUser?.name}
Toggle roles on/off for this user.
{rolesQ.isLoading ? (
) : rolesData?.roles ? (
{rolesData.roles.map((r) => ( ))}
) : (

No roles available.

)}
); }