Task #76: Show roles management inside the Groups editor
Adds a "Roles" tab to the GroupDetailEditor in the admin Groups screen so
admins can manage which roles are auto-granted to members of a group.
Changes:
- OpenAPI: added `GET /roles` (operationId `listRoles`) and a `Role` schema
in `lib/api-spec/openapi.yaml`. Ran `pnpm --filter @workspace/api-spec
run codegen` to regenerate `lib/api-client-react` and `lib/api-zod`.
- API server: added `GET /api/roles` (admin-only) in
`artifacts/api-server/src/routes/groups.ts`, returning all roles
ordered by name.
- Frontend (`artifacts/teaboy-os/src/pages/admin.tsx`):
- Imported `useListRoles` / `getListRolesQueryKey`.
- Added `roleIds` state and a fourth `"roles"` tab to GroupDetailEditor
with a checkbox list backed by the new endpoint, hydrated from
`group.roleIds`.
- Save now sends `roleIds: Array.from(roleIds)` via the existing
`PATCH /api/groups/:id` mutation.
- On save, also invalidate `getGetGroupQueryKey(editingGroupId)` so the
detail cache (30s staleTime) reflects the new role list immediately
when the editor is reopened.
- Translations: added `admin.groups.tab.roles`, `admin.groups.rolesHint`,
and `admin.groups.rolesEmpty` in both `en.json` and `ar.json`.
Verification:
- `pnpm -w run typecheck` passes.
- `pnpm --filter @workspace/api-server test` — 42/42 pass.
- e2e (testing skill): logged in as admin, opened TeaBoy group, toggled
the `user` role on the new Roles tab, saved, reopened — toggle
persisted.
No deviations from the task description.
Replit-Task-Id: 42808400-9209-469b-b526-37c776ffb25d
This commit is contained in:
@@ -169,6 +169,16 @@ export interface AddUserRoleBody {
|
||||
roleName: string;
|
||||
}
|
||||
|
||||
export interface Role {
|
||||
id: number;
|
||||
name: string;
|
||||
/** @nullable */
|
||||
descriptionAr?: string | null;
|
||||
/** @nullable */
|
||||
descriptionEn?: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface Group {
|
||||
id: number;
|
||||
name: string;
|
||||
|
||||
@@ -53,6 +53,7 @@ import type {
|
||||
RequestUploadUrlBody,
|
||||
RequestUploadUrlResponse,
|
||||
ResetPasswordBody,
|
||||
Role,
|
||||
SendMessageBody,
|
||||
Service,
|
||||
ServiceCategory,
|
||||
@@ -4673,6 +4674,71 @@ export const useRemoveUserRole = <
|
||||
return useMutation(getRemoveUserRoleMutationOptions(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary List roles (admin)
|
||||
*/
|
||||
export const getListRolesUrl = () => {
|
||||
return `/api/roles`;
|
||||
};
|
||||
|
||||
export const listRoles = async (options?: RequestInit): Promise<Role[]> => {
|
||||
return customFetch<Role[]>(getListRolesUrl(), {
|
||||
...options,
|
||||
method: "GET",
|
||||
});
|
||||
};
|
||||
|
||||
export const getListRolesQueryKey = () => {
|
||||
return [`/api/roles`] as const;
|
||||
};
|
||||
|
||||
export const getListRolesQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listRoles>>,
|
||||
TError = ErrorType<unknown>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<Awaited<ReturnType<typeof listRoles>>, TError, TData>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListRolesQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listRoles>>> = ({
|
||||
signal,
|
||||
}) => listRoles({ signal, ...requestOptions });
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listRoles>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListRolesQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listRoles>>
|
||||
>;
|
||||
export type ListRolesQueryError = ErrorType<unknown>;
|
||||
|
||||
/**
|
||||
* @summary List roles (admin)
|
||||
*/
|
||||
|
||||
export function useListRoles<
|
||||
TData = Awaited<ReturnType<typeof listRoles>>,
|
||||
TError = ErrorType<unknown>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<Awaited<ReturnType<typeof listRoles>>, TError, TData>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListRolesQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List groups (admin)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user