From db444a0767b47d2a4af4ad555f7d79ee018649f9 Mon Sep 17 00:00:00 2001 From: mijinummi Date: Mon, 23 Feb 2026 18:21:54 +0100 Subject: [PATCH 1/2] feat(users): build users management page with filtering and role management (#460) --- frontend/app/hooks/useUsers.ts | 17 +++++ frontend/app/users/page.tsx | 108 ++++++++++++++++++++++++++++++ frontend/components/ui/Avatar.tsx | 22 ++++++ frontend/lib/api/usersApi.ts | 11 +++ 4 files changed, 158 insertions(+) create mode 100644 frontend/app/hooks/useUsers.ts create mode 100644 frontend/app/users/page.tsx create mode 100644 frontend/components/ui/Avatar.tsx create mode 100644 frontend/lib/api/usersApi.ts diff --git a/frontend/app/hooks/useUsers.ts b/frontend/app/hooks/useUsers.ts new file mode 100644 index 00000000..d7133fc5 --- /dev/null +++ b/frontend/app/hooks/useUsers.ts @@ -0,0 +1,17 @@ +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { getUsers, updateUserRole } from '../lib/api/usersApi'; + +export function useUsers() { + return useQuery({ queryKey: ['users'], queryFn: getUsers }); +} + +export function useUpdateUserRole() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ id, role }: { id: string; role: string }) => + updateUserRole(id, role), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users'] }); + }, + }); +} diff --git a/frontend/app/users/page.tsx b/frontend/app/users/page.tsx new file mode 100644 index 00000000..287b41f0 --- /dev/null +++ b/frontend/app/users/page.tsx @@ -0,0 +1,108 @@ +'use client'; + +import React, { useState } from 'react'; +import { useUsers, useUpdateUserRole } from '../hooks/useUsers'; +import { Avatar } from '../../components/ui/Avatar'; + +export default function UsersPage() { + const { data: users = [], isLoading } = useUsers(); + const updateRole = useUpdateUserRole(); + + const [search, setSearch] = useState(''); + const [roleFilter, setRoleFilter] = useState(''); + + if (isLoading) return
Loading...
; + + const filteredUsers = users.filter((u: any) => + (u.name.toLowerCase().includes(search.toLowerCase()) || + u.email.toLowerCase().includes(search.toLowerCase())) && + (roleFilter ? u.role === roleFilter : true) + ); + + return ( +
+

Users Management

+ +
+ setSearch(e.target.value)} + className="border p-2" + /> + +
+ + + + + + + + + + + + {filteredUsers.map((user: any) => ( + + + + + + + ))} + +
Avatar + NameEmailRoleJoined
+ + {user.name}{' '} + {user.isCurrentUser && ( + + You + + )} + {user.email} + + {new Date(user.joinedAt).toLocaleDateString()}
+ +
+

Role Legend

+
    +
  • + Admin — full access +
  • +
  • + Manager — manage teams +
  • +
  • + Staff — standard user +
  • +
+
+
+ ); +} diff --git a/frontend/components/ui/Avatar.tsx b/frontend/components/ui/Avatar.tsx new file mode 100644 index 00000000..e03dc214 --- /dev/null +++ b/frontend/components/ui/Avatar.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +export function Avatar({ + firstName, + lastName, + isCurrentUser, +}: { + firstName: string; + lastName: string; + isCurrentUser?: boolean; +}) { + const initials = `${firstName[0]}${lastName[0]}`.toUpperCase(); + const bgColor = isCurrentUser ? 'bg-gray-800' : 'bg-gray-400'; + + return ( +
+ {initials} +
+ ); +} diff --git a/frontend/lib/api/usersApi.ts b/frontend/lib/api/usersApi.ts new file mode 100644 index 00000000..71b9bfe8 --- /dev/null +++ b/frontend/lib/api/usersApi.ts @@ -0,0 +1,11 @@ +import api from './client'; + +export async function getUsers() { + const res = await api.get('/api/users'); + return res.data; +} + +export async function updateUserRole(id: string, role: string) { + const res = await api.patch(`/api/users/${id}/role`, { role }); + return res.data; +} From 73aef98f48f0a4f7fa25b0f941b960d3ae8bb01f Mon Sep 17 00:00:00 2001 From: mijinummi Date: Mon, 23 Feb 2026 18:35:24 +0100 Subject: [PATCH 2/2] feat(users): add React Query hooks for users and settings (#464) --- frontend/app/hooks/useUsers.ts | 32 +++++++++++++++++++++++++++++--- frontend/lib/api/reportsApi.ts | 7 +++++++ frontend/lib/api/usersApi.ts | 10 ++++++++-- frontend/lib/users.ts | 13 +++++++++++++ 4 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 frontend/lib/api/reportsApi.ts create mode 100644 frontend/lib/users.ts diff --git a/frontend/app/hooks/useUsers.ts b/frontend/app/hooks/useUsers.ts index d7133fc5..aadc2430 100644 --- a/frontend/app/hooks/useUsers.ts +++ b/frontend/app/hooks/useUsers.ts @@ -1,8 +1,14 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { getUsers, updateUserRole } from '../lib/api/usersApi'; +import { getUsers, updateUserRole, updateProfile } from '../lib/api/usersApi'; +import { getReportsSummary } from '../lib/api/reportsApi'; +import { User, ReportSummary } from '../lib/types/users'; +import { useAuthStore } from '../store/authStore'; -export function useUsers() { - return useQuery({ queryKey: ['users'], queryFn: getUsers }); +export function useUsersList() { + return useQuery({ + queryKey: ['users'], + queryFn: getUsers, + }); } export function useUpdateUserRole() { @@ -15,3 +21,23 @@ export function useUpdateUserRole() { }, }); } + +export function useUpdateProfile() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ id, payload }: { id: string; payload: Partial }) => + updateProfile(id, payload), + onSuccess: (updatedUser) => { + // Update Zustand store to keep sidebar in sync + useAuthStore.getState().setUser(updatedUser); + queryClient.invalidateQueries({ queryKey: ['users'] }); + }, + }); +} + +export function useReportsSummary() { + return useQuery({ + queryKey: ['reportsSummary'], + queryFn: getReportsSummary, + }); +} diff --git a/frontend/lib/api/reportsApi.ts b/frontend/lib/api/reportsApi.ts new file mode 100644 index 00000000..c8564927 --- /dev/null +++ b/frontend/lib/api/reportsApi.ts @@ -0,0 +1,7 @@ +import api from './client'; +import { ReportSummary } from '../users'; + +export async function getReportsSummary(): Promise { + const res = await api.get('/api/reports/summary'); + return res.data; +} diff --git a/frontend/lib/api/usersApi.ts b/frontend/lib/api/usersApi.ts index 71b9bfe8..8737ae3b 100644 --- a/frontend/lib/api/usersApi.ts +++ b/frontend/lib/api/usersApi.ts @@ -1,11 +1,17 @@ import api from './client'; +import { User } from '../users'; -export async function getUsers() { +export async function getUsers(): Promise { const res = await api.get('/api/users'); return res.data; } -export async function updateUserRole(id: string, role: string) { +export async function updateUserRole(id: string, role: string): Promise { const res = await api.patch(`/api/users/${id}/role`, { role }); return res.data; } + +export async function updateProfile(id: string, payload: Partial): Promise { + const res = await api.patch(`/api/users/${id}`, payload); + return res.data; +} diff --git a/frontend/lib/users.ts b/frontend/lib/users.ts new file mode 100644 index 00000000..1a0e89e7 --- /dev/null +++ b/frontend/lib/users.ts @@ -0,0 +1,13 @@ +export interface User { + id: string; + name: string; + email: string; + role: 'admin' | 'manager' | 'staff'; + joinedAt: string; +} + +export interface ReportSummary { + totalUsers: number; + activeUsers: number; + inactiveUsers: number; +}