diff --git a/overseerr-api.yml b/overseerr-api.yml index 5c9ae45a6..dc1c5067e 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -2669,6 +2669,13 @@ paths: description: Returns all users in a JSON array. tags: - users + parameters: + - in: query + name: sort + schema: + type: string + enum: [created, updated, requests, displayname] + default: created responses: '200': description: A JSON array of all users diff --git a/server/routes/user.ts b/server/routes/user.ts index 45117247d..04e2c4c77 100644 --- a/server/routes/user.ts +++ b/server/routes/user.ts @@ -11,10 +11,35 @@ import { UserType } from '../constants/user'; const router = Router(); -router.get('/', async (_req, res) => { - const userRepository = getRepository(User); +router.get('/', async (req, res) => { + let query = getRepository(User).createQueryBuilder('user'); - const users = await userRepository.find(); + switch (req.query.sort) { + case 'updated': + query = query.orderBy('user.updatedAt', 'DESC'); + break; + case 'displayname': + query = query.orderBy( + '(CASE WHEN user.username IS NULL THEN user.plexUsername ELSE user.username END)', + 'ASC' + ); + break; + case 'requests': + query = query + .addSelect((subQuery) => { + return subQuery + .select('COUNT(request.id)', 'requestCount') + .from(MediaRequest, 'request') + .where('request.requestedBy.id = user.id'); + }, 'requestCount') + .orderBy('requestCount', 'DESC'); + break; + default: + query = query.orderBy('user.id', 'ASC'); + break; + } + + const users = await query.getMany(); return res.status(200).json(User.filterMany(users)); }); diff --git a/src/components/RequestList/index.tsx b/src/components/RequestList/index.tsx index 39dca9124..741729f3e 100644 --- a/src/components/RequestList/index.tsx +++ b/src/components/RequestList/index.tsx @@ -56,11 +56,11 @@ const RequestList: React.FC = () => { return ( <> -
+
{intl.formatMessage(messages.requests)}
-
-
- +
+
+ { setPageIndex(0); setCurrentFilter(e.target.value as Filter); }} - onBlur={(e) => { - setPageIndex(0); - setCurrentFilter(e.target.value as Filter); - }} value={currentFilter} - className="rounded-r-only" + className="text-sm rounded-r-only" >
-
- +
+ { setCurrentSort(e.target.value as Sort); }} value={currentSort} - className="rounded-r-only" + className="text-sm rounded-r-only" >
+
{intl.formatMessage(messages.userlist)}
-
- - +
+
+ + +
+
+ + + + + + +
@@ -404,7 +448,7 @@ const UserList: React.FC = () => { /> )} - {intl.formatMessage(messages.username)} + {intl.formatMessage(messages.user)}{intl.formatMessage(messages.totalrequests)}{intl.formatMessage(messages.usertype)}{intl.formatMessage(messages.role)} diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 3584943c2..b4946550b 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -628,6 +628,10 @@ "components.UserList.passwordinfodescription": "Email notifications need to be configured and enabled in order to automatically generate passwords.", "components.UserList.plexuser": "Plex User", "components.UserList.role": "Role", + "components.UserList.sortCreated": "Creation Date", + "components.UserList.sortDisplayName": "Display Name", + "components.UserList.sortRequests": "Request Count", + "components.UserList.sortUpdated": "Last Updated", "components.UserList.totalrequests": "Total Requests", "components.UserList.user": "User", "components.UserList.usercreatedfailed": "Something went wrong while creating the user.", @@ -635,7 +639,6 @@ "components.UserList.userdeleted": "User deleted.", "components.UserList.userdeleteerror": "Something went wrong while deleting the user.", "components.UserList.userlist": "User List", - "components.UserList.username": "Username", "components.UserList.users": "Users", "components.UserList.userssaved": "Users saved!", "components.UserList.usertype": "User Type",