Skip to content

Commit

Permalink
Merge pull request #131 from SELab-2/adminpanel
Browse files Browse the repository at this point in the history
admin pagina
  • Loading branch information
reyniersbram authored Apr 19, 2024
2 parents 67344b8 + 51dcbd2 commit c3ca834
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 16 deletions.
10 changes: 10 additions & 0 deletions frontend/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ export default {
subjects: "My subjects",
announcements: "Announcements",
},
admin: {
users: "Users",
userTable: {
name: "Name",
uid: "UGent ID",
email: "Email",
isTeacher: "Is Teacher",
isAdmin: "Is Admin",
},
},
subject: {
register: "Register to subject:",
academy_year: "Academic year",
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/i18n/locales/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,16 @@ export default {
subjects: "Mijn vakken",
announcements: "Meldingen",
},

admin: {
users: "Gebruikers",
userTable: {
name: "Naam",
uid: "UGent ID",
email: "Email",
isTeacher: "Is Lesgever",
isAdmin: "Is Beheerder",
},
},
subject: {
register: "Registreer bij vak:",
academy_year: "Academiejaar",
Expand Down
71 changes: 59 additions & 12 deletions frontend/src/queries/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ import {
type UseMutationReturnType,
} from "@tanstack/vue-query";
import type User from "@/models/User";
import { getMySubjects, getUser, toggleAdmin } from "@/services/user";
import { getMySubjects, getUser, getUsers, toggleAdmin, toggleTeacher } from "@/services/user";
import { type Ref, computed } from "vue";
import type { UserSubjectList } from "@/models/Subject";

function USER_QUERY_KEY(uid: string | null): string[] {
return uid ? ["user", uid] : ["user"];
}

function USERS_QUERY_KEY(): string[] {
return ["users"];
}

export function useUserQuery(uid: Ref<string | undefined> | null): UseQueryReturnType<User, Error> {
return useQuery<User, Error>({
queryKey: computed(() => USER_QUERY_KEY(uid?.value!)),
Expand All @@ -22,27 +26,70 @@ export function useUserQuery(uid: Ref<string | undefined> | null): UseQueryRetur
});
}

// TODO: Now only toggles current user
export function useToggleAdminMutation(): UseMutationReturnType<void, Error, User, void> {
export function useUsersQuery(): UseQueryReturnType<User[], Error> {
return useQuery<User[], Error>({
queryKey: USERS_QUERY_KEY(),
queryFn: () => getUsers(),
});
}

// TODO: Use USER_QUERY_KEY(uid) instead of USERS_QUERY_KEY() for invalidation
function useToggleMutation(
toggleFn: (uid: string) => Promise<User>,
getField: (_: User) => boolean,
setField: (_: User, value: boolean) => void
): UseMutationReturnType<User, Error, string, { previousUsers: User[] }> {
const queryClient = useQueryClient();
return useMutation({
mutationFn: toggleAdmin,
onMutate: async (user: User) => {
await queryClient.cancelQueries({ queryKey: USER_QUERY_KEY(null) });
queryClient.setQueryData<User>(USER_QUERY_KEY(null), () => {
return { ...user, is_admin: !user.is_admin };
mutationFn: async (uid) => await toggleFn(uid),
onMutate: async (uid: string) => {
const users = queryClient.getQueryData<User[]>(USERS_QUERY_KEY());
await queryClient.cancelQueries({ queryKey: USERS_QUERY_KEY() });
queryClient.setQueryData<User[]>(USERS_QUERY_KEY(), () => {
return users?.map((user: User) => {
const mappedUser = { ...user };
setField(mappedUser, user.uid === uid ? !getField(user) : getField(user));
return mappedUser;
});
});
return { previousUsers: users! };
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: USER_QUERY_KEY(null) });
onSettled: (_, __, uid, ctx) => {

Check warning on line 57 in frontend/src/queries/User.ts

View workflow job for this annotation

GitHub Actions / Run linters

'_' is defined but never used

Check warning on line 57 in frontend/src/queries/User.ts

View workflow job for this annotation

GitHub Actions / Run linters

'__' is defined but never used

Check warning on line 57 in frontend/src/queries/User.ts

View workflow job for this annotation

GitHub Actions / Run linters

'uid' is defined but never used

Check warning on line 57 in frontend/src/queries/User.ts

View workflow job for this annotation

GitHub Actions / Run linters

'ctx' is defined but never used
queryClient.invalidateQueries({ queryKey: USERS_QUERY_KEY() });
},
onError: (_, user) => {
onError: (_, uid, ctx) => {
queryClient.setQueryData<User[]>(USERS_QUERY_KEY(), () => ctx!.previousUsers!);
alert("Could not update user");
queryClient.setQueryData<User>(USER_QUERY_KEY(null), () => user!);
},
});
}

export function useToggleAdminMutation(): UseMutationReturnType<
User,
Error,
string,
{ previousUsers: User[] }
> {
return useToggleMutation(
toggleAdmin,
(user) => user.is_admin,
(user, value) => (user.is_admin = value)
);
}

export function useToggleTeacherMutation(): UseMutationReturnType<
User,
Error,
string,
{ previousUsers: User[] }
> {
return useToggleMutation(
toggleTeacher,
(user) => user.is_teacher,
(user, value) => (user.is_teacher = value)
);
}

// Hook for fetching subjects for a user
export function useMySubjectsQuery(): UseQueryReturnType<UserSubjectList, Error> {
return useQuery<UserSubjectList, Error>({
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/services/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@ export async function getUser(uid?: string): Promise<User> {
return authorized_fetch(`/api/users/${uid || "me"}`, { method: "GET" });
}

export async function toggleAdmin() {
return authorized_fetch<void>("/api/users/me", { method: "POST" });
export async function getUsers(): Promise<User[]> {
return authorized_fetch("/api/users", { method: "GET" });
}

export async function toggleAdmin(uid: string): Promise<User> {
return authorized_fetch<User>(`/api/users/${uid}/admin`, { method: "POST" });
}

export async function toggleTeacher(uid: string): Promise<User> {
return authorized_fetch<User>(`/api/users/${uid}/teacher`, { method: "POST" });
}

// Fetches all subjects for logged in user
Expand Down
126 changes: 125 additions & 1 deletion frontend/src/views/AdminView.vue
Original file line number Diff line number Diff line change
@@ -1,3 +1,127 @@
<template>
<h1>Admin Panel</h1>
<div class="adminpanel">
<v-card :title="$t('admin.users')" flat class="bg-white">
<v-card-title>
<v-text-field
prepend-inner-icon="mdi-magnify"
v-model="search"
label="Search"
single-line
hide-details
></v-text-field>
</v-card-title>
<v-data-table-virtual
:headers="headers"
:items="users"
:search="search"
v-model:sortBy="sortBy"
item-value="uid"
:loading="isUserLoading || isUsersLoading"
density="compact"
class="table"
>
<template v-slot:loading>
<v-skeleton-loader type="table-row@15" class="table"></v-skeleton-loader>
</template>
<template v-slot:[`item.is_teacher`]="{ item }">
<v-checkbox-btn
:model-value="item.is_teacher"
:disabled="item.uid === currentUser?.uid"
@update:model-value="() => onToggleTeacher(item)"
></v-checkbox-btn>
</template>
<template v-slot:[`item.is_admin`]="{ item }">
<v-checkbox-btn
:model-value="item.is_admin"
:disabled="item.uid === currentUser?.uid"
@update:model-value="() => onToggleAdmin(item)"
></v-checkbox-btn>
</template>
</v-data-table-virtual>
</v-card>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import {
useUserQuery,
useUsersQuery,
useToggleAdminMutation,
useToggleTeacherMutation,
} from "@/queries/User";
import type User from "@/models/User";
const { t } = useI18n();
const { data: currentUser, isLoading: isUserLoading } = useUserQuery(null);
const { data: users, isLoading: isUsersLoading } = useUsersQuery();
const { mutateAsync: toggleAdmin } = useToggleAdminMutation();
const { mutateAsync: toggleTeacher } = useToggleTeacherMutation();
/**
* Sorts boolean values in descending order.
*/
function sortBool(a: boolean, b: boolean): number {
return a === b ? 0 : a ? -1 : 1;
}
const search = ref("");
const sortBy = ref([{ key: "given_name", order: "asc" }]);
async function onToggleAdmin(user: User) {
await toggleAdmin(user.uid);
}
async function onToggleTeacher(user: User) {
await toggleTeacher(user.uid);
}
const headers = ref([
{
title: computed(() => t("admin.userTable.name")),
key: "given_name",
align: "start",
sortable: true,
},
{
title: computed(() => t("admin.userTable.uid")),
key: "uid",
align: "start",
sortable: false,
},
{
title: computed(() => t("admin.userTable.email")),
key: "mail",
align: "start",
sortable: false,
},
{
title: computed(() => t("admin.userTable.isTeacher")),
key: "is_teacher",
sortable: true,
filterable: false,
filter: () => true, // disable filter
sort: sortBool,
},
{
title: computed(() => t("admin.userTable.isAdmin")),
key: "is_admin",
sortable: true,
filterable: false,
filter: () => true, // disable filter
sort: sortBool,
},
]);
</script>
<style scoped>
.adminpanel {
margin: 15px;
}
.table {
color: black !important;
background-color: white;
width: 95%;
}
</style>

0 comments on commit c3ca834

Please sign in to comment.