Skip to content

Commit

Permalink
api routes en zoeken toegevoegd
Browse files Browse the repository at this point in the history
  • Loading branch information
badduck32 committed May 2, 2024
1 parent 709153b commit 439e61c
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 81 deletions.
28 changes: 15 additions & 13 deletions frontend/src/@types/requests.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export enum ApiRoutes {

COURSE = "api/courses/:courseId",
COURSE_MEMBERS = "api/courses/:courseId/members",
COURSE_MEMBER = "api/courses/:courseId/members/:userId",
COURSE_PROJECTS = "api/courses/:id/projects",
COURSE_CLUSTERS = "api/courses/:id/clusters",
COURSE_GRADES = '/api/courses/:id/grades',
Expand Down Expand Up @@ -39,7 +38,7 @@ export enum ApiRoutes {

TEST = "api/test",
USER = "api/users/:id",
USERS = "api/users",
USERS = "api/users?:params",
USER_AUTH = "api/user",
}

Expand Down Expand Up @@ -87,30 +86,32 @@ export type DELETE_Requests = {
[ApiRoutes.PROJECT]: undefined
[ApiRoutes.GROUP_MEMBER]: undefined
[ApiRoutes.COURSE_LEAVE]: undefined
[ApiRoutes.COURSE_MEMBER]: undefined
}


/**
* the body of the PUT & PATCH requests
* the body of the PUT requests
*/
export type PUT_Requests = {
[ApiRoutes.COURSE]: POST_Requests[ApiRoutes.COURSE]
[ApiRoutes.PROJECT]: ProjectFormData
[ApiRoutes.COURSE_MEMBER]: { relation: CourseRelation }
[ApiRoutes.PROJECT_SCORE]: { score: number | null , feedback: string}
[ApiRoutes.USER]: {
name: string
surname: string
email: string
role: UserRole
}
}



/**
* The response you get from the PUT request
*/
export type PUT_Responses = {
[ApiRoutes.COURSE]: GET_Responses[ApiRoutes.COURSE]
[ApiRoutes.PROJECT]: GET_Responses[ApiRoutes.PROJECT]
[ApiRoutes.COURSE_MEMBER]: GET_Responses[ApiRoutes.COURSE_MEMBERS]
[ApiRoutes.PROJECT_SCORE]: GET_Responses[ApiRoutes.PROJECT_SCORE]
[ApiRoutes.USER]: GET_Responses[ApiRoutes.USER]
}


type CourseTeacher = {
name: string
surname: string
Expand Down Expand Up @@ -203,11 +204,12 @@ export type GET_Responses = {
}
[ApiRoutes.USERS]: {
name: string
userId: number
surname: string
id: number
url: string
email: string
role: UserRole
}
}[]
[ApiRoutes.GROUP_MEMBERS]: GET_Responses[ApiRoutes.GROUP_MEMBER][]

[ApiRoutes.COURSE_CLUSTERS]: GET_Responses[ApiRoutes.CLUSTER][]
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/i18n/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@
"editRole": "Edit a user's role"
},
"editRole": {
"email": "Email",
"name": "Name",
"surname": "Surname",
"search": "Search",
"emailError": "Please enter a valid email",
"nameError": "Name must be at least 3 characters long",
"surnameError": "Surname must be at least 3 characters long",
"searchTutorial": "Enter a name, surname, or email and press \"Search\" to find users.",
"noUsersFound": "No users found",
"student": "Student",
"teacher": "Teacher",
"admin": "Admin",
Expand Down Expand Up @@ -98,7 +107,6 @@
"options": "Update",
"groupMembers": "Group members",
"newProject": "New project",
"scoreTooHigh": "Score is higher than maximum score",
"change": {
"title": "Create project",
"updateTitle": "Update {{name}}",
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/i18n/nl/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@
},

"editRole": {
"email": "Email",
"name": "Naam",
"surname": "Achternaam",
"search": "Zoeken",
"emailError": "Vul een geldig email adres in",
"nameError": "Naam moet minstens 3 karakters lang zijn",
"surnameError": "Achternaam moet minstens 3 karakters lang zijn",
"searchTutorial": "Vul een email adres, naam of achternaam in en druk dan op \"Zoeken\" om gebruikers op te zoeken.",
"noUsersFound": "Geen gebruikers gevonden",
"student": "Student",
"teacher": "Professor",
"admin": "Admin",
Expand Down Expand Up @@ -101,7 +110,6 @@
"options": "Aanpassen",
"groupMembers": "Groepsleden",
"newProject": "Nieuw project",
"scoreTooHigh": "Score is hoger dan maximum score",
"change": {
"title": "Maak project aan",
"name": "Naam",
Expand Down
173 changes: 115 additions & 58 deletions frontend/src/pages/editRole/EditRole.tsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,132 @@
import { useEffect, useState } from "react";
import { Spin } from "antd";
import { Row, Col, Form, Input, Button, Spin } from "antd";
import UserList from "./components/UserList"
import { ApiRoutes, GET_Responses, UserRole } from "../../@types/requests.d";
import apiCall from "../../util/apiFetch";
import { useTranslation } from "react-i18next";
import { UsersListItem } from "./components/UserList";

export type UsersType = GET_Responses[ApiRoutes.USERS]

const ProfileContent = () => {
const [users, setUsers] = useState<UsersType[] | null>(null);
const [users, setUsers] = useState<UsersType | null>(null);
const [searched, setSearched] = useState(false);
const [form] = Form.useForm();
const { t } = useTranslation();

function updateRole(user: UsersType, role: UserRole) {
//TODO: PUT of PATCH call
console.log("User: ", user);
console.log("Role: ", role);
if(!users) return;
const updatedUsers = users.map((u) => {
if (u.userId === user.userId) {
return { ...u, role: role };
}
return u;
});
setUsers(updatedUsers);
function updateRole(user: UsersListItem, role: UserRole) {
//here user is of type User (not UsersListItem), but it seems to work because the needed properties are named the same
console.log(user)
apiCall.patch(ApiRoutes.USER, {role: role}, {id: user.id}).then((res) => {
console.log(res.data);
//replace this user in the userlist with the updated one from res.data
const updatedUsers = users?.map((u) => {
if (u.id === user.id) {
return { ...u, role: res.data.role };
}
return u;
});
setUsers(updatedUsers?updatedUsers:null);
})
}

useEffect(() => {
//TODO: moet met GET call
/*apiCall.get(ApiRoutes.USERS).then((res) => {
console.log(res.data)
setUsers(res.data)
})*/
setUsers([
{
userId: 1,
name: "Alice Kornelis",
role: "student",
email: "[email protected]",
url: "test"
},
{
userId: 2,
name: "Bob Kornelis",
role: "teacher",
email: "[email protected]",
url: "test"
},
{
userId: 3,
name: "Charlie Kornelis",
role: "admin",
email: "[email protected]",
url: "test"
}
]);
}, []);
const onSearch = (values: any) => {
setSearched(true);
setUsers(null);
//search operation here
const params = Object.entries(values)
.filter(([key, value]) => value !== undefined)
.reduce((obj, [key, value]) => ({ ...obj, [key]: value }), {});
const queryString = Object.entries(params)
.map(([key, value]) => `${key}=${value}`)
.join('&');
console.log(queryString);
apiCall.get(ApiRoutes.USERS,{params:queryString}).then((res) => {
console.log(res.data)
setUsers(res.data);
})

};

if (users === null) {
return (
<div style={{ width: "100%", height: "100%", display: "flex", justifyContent: "center", alignItems: "center" }}>
<Spin
tip="Loading..."
size="large"
/>
</div>
)
}
return (
<div style={{padding: "3rem"}}>
<Form
form={form}
name="search"
onFinish={onSearch}
initialValues={{ remember: true }}
>
<Row gutter={24} justify="space-between">
<Col span={7}>
<Form.Item
name="email"
rules={[
{ type: 'email', message: t("editRole.emailError") },
]}
>
<Input placeholder={t("editRole.email")} />
</Form.Item>
</Col>

return (
<div style={{padding: "3rem"}}>
<UserList users={users} updateRole={updateRole} />
<Col span={7}>
<Form.Item
name="name"
rules={[
{ min: 3, message: t("editRole.nameError") },
]}
>
<Input placeholder={t("editRole.name")} />
</Form.Item>
</Col>

<Col span={7}>
<Form.Item
name="surname"
rules={[
{ min: 3, message: t("editRole.surnameError") },
]}
>
<Input placeholder={t("editRole.surname")} />
</Form.Item>
</Col>

<Col span={3}>
<Form.Item shouldUpdate>
{() => (
<Button
type="primary"
htmlType="submit"
disabled={
['name', 'surname', 'email'].every(field => !form.getFieldValue(field)) ||
form.getFieldsError().filter(({ errors }) => errors.length).length > 0
}
style={{ width: '100%' }}
>
{t("editRole.search")}
</Button>
)}
</Form.Item>
</Col>
</Row>
</Form>
{searched ? (
users === null ? (
<div style={{ width: "100%", height: "100%", display: "flex", justifyContent: "center", alignItems: "center" }}>
<Spin
tip="Loading..."
size="large"
/>
</div>
) : (
<UserList users={users} updateRole={updateRole} />
)
) : (
<div style={{ textAlign: 'center', marginTop: '2rem' }}>
<p>{t("editRole.searchTutorial")}</p>
</div>
);
)}
</div>
);
};

export function EditRole() {
Expand Down
34 changes: 26 additions & 8 deletions frontend/src/pages/editRole/components/UserList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ import { useTranslation } from "react-i18next"
import { UserRole } from "../../../@types/requests"
import { useState } from "react"
import { UsersType } from "../EditRole"
import { GET_Responses, ApiRoutes } from "../../../@types/requests.d"
import { User } from "../../../providers/UserProvider"

const UserList: React.FC<{ users: UsersType[]; updateRole: (user: UsersType, role: UserRole) => void }> = ({ users, updateRole }) => {
//this is ugly, but if I put this in GET_responses, it will be confused with the User type (and there's no GET request with this as a response).
//this is also the only place this is used, so I think it's fine.
export type UsersListItem = { name: string, surname: string, id: number, url: string, email: string, role: UserRole }

const UserList: React.FC<{ users: UsersType; updateRole: (user: UsersListItem, role: UserRole) => void }> = ({ users, updateRole }) => {
const { t } = useTranslation()
const [visible, setVisible] = useState(false)
const [selectedUser, setSelectedUser] = useState<UsersType | null>(null)
const [selectedUser, setSelectedUser] = useState<UsersListItem | null>(null)
const [selectedRole, setSelectedRole] = useState<UserRole | null>(null)

const handleMenuClick = (user: UsersType, role: UserRole) => {
const handleMenuClick = (user: UsersListItem, role: UserRole) => {
setSelectedUser(user)
setSelectedRole(role)
setVisible(true)
Expand All @@ -27,9 +33,20 @@ const UserList: React.FC<{ users: UsersType[]; updateRole: (user: UsersType, rol
setVisible(false)
}

const renderUserItem = (user: UsersType) => (
//sort based on name, then surname, then email
const sortedUsers = [...users].sort((a, b) => {
const nameComparison = a.name.localeCompare(b.name);
if (nameComparison !== 0) return nameComparison;

const surnameComparison = a.surname.localeCompare(b.surname);
if (surnameComparison !== 0) return surnameComparison;

return a.email.localeCompare(b.email);
});

const renderUserItem = (user: UsersListItem) => (
<List.Item>
<List.Item.Meta title={user.name} />
<List.Item.Meta title={user.name + " " + user.surname} description={user.email} />
<Dropdown
trigger={["click"]}
placement="bottomRight"
Expand All @@ -49,7 +66,7 @@ const UserList: React.FC<{ users: UsersType[]; updateRole: (user: UsersType, rol
},
],
selectedKeys: [user.role],
onClick: (e) => handleMenuClick(user, e.key as UserRole),
onClick: (e) => handleMenuClick(user, e.key as UserRole),
}}
>
<a onClick={(e) => e.preventDefault()}>
Expand All @@ -66,8 +83,9 @@ const UserList: React.FC<{ users: UsersType[]; updateRole: (user: UsersType, rol
<div>
<List
itemLayout="horizontal"
dataSource={users}
dataSource={sortedUsers}
renderItem={renderUserItem}
locale={{ emptyText: t("editRole.noUsersFound") }}
/>
<div>
<Modal
Expand All @@ -76,7 +94,7 @@ const UserList: React.FC<{ users: UsersType[]; updateRole: (user: UsersType, rol
onOk={handleConfirm}
onCancel={onCancel}
>
{t("editRole.confirmationText",{role: selectedRole, name: selectedUser?.name })}
{t("editRole.confirmationText",{role: selectedRole, name: selectedUser?.name + " " + selectedUser?.surname})}
</Modal>
</div>
</div>
Expand Down

0 comments on commit 439e61c

Please sign in to comment.