Skip to content

Commit

Permalink
Project view (#338)
Browse files Browse the repository at this point in the history
* feat: endpoint for eagerly fetching all projects for students/teachers/assistants

* fix: tests

* feat: editor for course description and editor, better project overview list

* fix: tests

* chore: improved project progressbar, linting

* improvement: skeleton loaders for project list

* chore: merged migrations, updated tests

* chore: merge

* chore: better display of HTML description

* chore: merge

* chore: translations, import fixes and linting

* chore: tests

* chore: linting

* fix: typing

* fix: linting

* chore: removed vue-tsc

* feat: project view (wip)

* fix: better models and student project view

* feat: start project view for teachers

* feat: student project view, start teacher project view

* fix: redundant directory

---------

Co-authored-by: francis <francis.vauterin@ugent.be>
  • Loading branch information
EwoutV and francisvaut authored Apr 18, 2024
1 parent 441830a commit 1ed52e7
Show file tree
Hide file tree
Showing 35 changed files with 693 additions and 499 deletions.
2 changes: 1 addition & 1 deletion development.sh
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ if [ "$data" != "" ]; then
rm -f db.sqlite3 > /dev/null 2>&1

echo "Setting up workspace..."
python -m venv .venv_dev > /dev/null
python3 -m venv .venv_dev > /dev/null
source .venv_dev/bin/activate
pip install poetry > /dev/null
poetry install > /dev/null
Expand Down
11 changes: 8 additions & 3 deletions frontend/src/assets/lang/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,14 @@
"coming": "Aankomende deadlines",
"deadline": "Deadline",
"days": "Vandaag om {hour} | Morgen om {hour} | Over {count} dagen",
"groupName": "Groepsnaam",
"groupPopulation": "Grootte",
"groupStatus": "Status",
"start": "Startdatum",
"submissionStatus": "Indienstatus",
"group": "Groep",
"groupSize": "Individueel | Groepen van {count} personen",
"noGroups": "Geen groepen beschikbaar",
"groupMembers": "Groepsleden",
"chooseGroup": "Kies een groep",
"joinGroup": "Kies groep",
Expand All @@ -72,6 +77,7 @@
"submit": "Indienen",
"course": "Vak",
"chooseFile": "Kies bestand(en)",
"noSubmissions": "Geen indieningen beschikbaar",
"hoverText": {
"allChecksFailed": "Structuur en extra checks gefaald",
"allChecksPassed": "Alle checks geslaagd",
Expand Down Expand Up @@ -147,9 +153,8 @@
"open": "Details",
"newProject": "Nieuw project",
"noSubmissions": "Dit project heeft geen indieningen",
"singleSubmission": "Indiening",
"multipleSubmissions": "Indieningen",
"singleGroup": "Groep",
"submissions": "Indiening | Indieningen",
"groups": "Groep | Groepen",
"multipleGroups": "Groepen",
"testsSucceed": "Geslaagde testen",
"testsFail": "Gefaalde testen",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/courses/CourseList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import { watch } from 'vue';
/* Props */
interface Props {
detail?: boolean;
courses: Course[] | null;
cols?: number;
courses: Course[] | null;
}
withDefaults(defineProps<Props>(), {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/layout/Title.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ withDefaults(defineProps<{ color?: 'primary' | 'contrast' }>(), {
color: var(--primary-color);
position: relative;
display: inline-block;
line-height: 2.8rem;
&::after {
content: '';
Expand Down
141 changes: 68 additions & 73 deletions frontend/src/components/projects/ChooseGroupCard.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Dialog from 'primevue/dialog';
import Button from 'primevue/button';
import DataTable from 'primevue/datatable';
import Column from 'primevue/column';
import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStudents } from '@/composables/services/student.service.ts';
import { storeToRefs } from 'pinia';
import { useAuthStore } from '@/store/authentication.store.ts';
import { type Group } from '@/types/Group.ts';
import { PrimeIcons } from 'primevue/api';
import Button from 'primevue/button';
import { type Student } from '@/types/users/Student.ts';
import { type Project } from '@/types/Project.ts';
/* Props */
defineProps<{
groups: Group[];
const props = defineProps<{
project: Project;
student: Student;
}>();
/* Composable injections */
const { t } = useI18n();
const { student } = storeToRefs(useAuthStore());
const { students, getStudentsByGroup, studentJoinGroup } = useStudents();
const { studentJoinGroup } = useStudents();
/* Emits */
const emit = defineEmits(['group-joined']);
Expand All @@ -26,86 +28,79 @@ const emit = defineEmits(['group-joined']);
const dialogVisible = ref<boolean>(false);
const selectedGroup = ref<Group | null>(null);
/**
* Shows the group dialog.
*
* @param group
*/
async function showGroupDialog(group: Group): Promise<void> {
selectedGroup.value = group;
await getStudentsByGroup(group.id);
watch(selectedGroup, () => {
dialogVisible.value = true;
}
});
/**
* Joins the selected group.
*
*/
async function joinSelectedGroup(): Promise<void> {
if (selectedGroup.value !== null && student.value !== null) {
await studentJoinGroup(selectedGroup.value.id, student.value.id);
dialogVisible.value = false;
if (selectedGroup.value !== null) {
await studentJoinGroup(selectedGroup.value.id, props.student.id);
emit('group-joined', selectedGroup.value);
selectedGroup.value = null;
dialogVisible.value = false;
}
}
</script>

<template>
<div>
<div class="groupcard">
<h2>{{ t('views.projects.chooseGroup') }}</h2>
<div v-if="groups">
<button
class="groupSelectionButton p-3"
v-for="group in groups"
:key="group.id"
@click="showGroupDialog(group)"
>
{{ t('views.projects.group') }} {{ group.id }}
</button>
</div>
<Dialog
v-model:visible="dialogVisible"
modal
:header="`${t('views.projects.group')} ${selectedGroup?.id}`"
:style="{ width: '25rem' }"
>
<ul v-if="students">
<li v-for="student in students" :key="student.id">
<div class="p-4 shadow-1">
<h2 class="mt-0">
{{ t('views.projects.chooseGroup') }}
</h2>
<template v-if="project.groups !== null && project.groups.length > 0 && !project.isLocked()">
<DataTable :value="project.groups" v-model:selection="selectedGroup" selection-mode="single">
<Column :header="t('views.projects.groupName')">
<template #body="{ data }">
{{ t('views.projects.group') }} {{ project.getGroupNumber(data) }}
</template>
</Column>
<Column :header="t('views.projects.groupPopulation')">
<template #body="{ data }">
<i class="pi pi-users mr-2" /> {{ data.getSize() }} / {{ project.group_size }}
</template>
</Column>
<Column :header="t('views.projects.groupStatus')">
<template #body="{ data }">
<i class="pi pi-lock" v-if="data.isLocked()" />
<i class="pi pi-unlock" v-else />
</template>
</Column>
</DataTable>
</template>
<template v-else>
{{ t('views.projects.noGroups') }}
</template>
<Dialog
v-if="selectedGroup !== null"
v-model:visible="dialogVisible"
modal
:header="`${t('views.projects.group')} ${project.getGroupNumber(selectedGroup)}`"
:style="{ width: '25rem' }"
>
<template v-if="selectedGroup.students.length > 0">
<ul v-if="selectedGroup.students" class="mt-0">
<li v-for="student in selectedGroup.students" :key="student.id">
{{ student.first_name }} {{ student.last_name }}
</li>
</ul>
<Button
class="mt-3"
@click="joinSelectedGroup"
:icon="PrimeIcons.ARROW_RIGHT"
:label="t('views.projects.joinGroup')"
icon-pos="right"
outlined
/>
</Dialog>
</div>
</template>
<template v-else>
{{ t('views.projects.noStudents') }}
</template>
<Button
class="mt-3"
@click="joinSelectedGroup"
:icon="PrimeIcons.ARROW_RIGHT"
:label="t('views.projects.joinGroup')"
icon-pos="right"
outlined
/>
</Dialog>
</div>
</template>

<style scoped lang="scss">
@import '@/assets/scss/theme/theme.scss';
.groupSelectionButton {
display: block;
background: none;
border: none;
text-align: left;
font-size: $fontSize;
padding: 0.5rem 1rem;
cursor: pointer;
color: $textSecondaryColor;
margin-bottom: 0.5rem;
border-bottom: 1px solid #e0e0e0;
:last-child {
border-bottom: none;
}
}
.groupSelectionButton:last-child {
border-bottom: none;
}
</style>
<style scoped lang="scss"></style>
57 changes: 10 additions & 47 deletions frontend/src/components/projects/JoinedGroupCard.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script setup lang="ts">
import { useStudents } from '@/composables/services/student.service.ts';
import { onMounted, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { type Group } from '@/types/Group.ts';
import { storeToRefs } from 'pinia';
Expand All @@ -18,17 +17,9 @@ const emit = defineEmits(['group-left']);
/* Composable injections */
const { student } = storeToRefs(useAuthStore());
const { students, getStudentsByGroup, studentLeaveGroup } = useStudents();
const { studentLeaveGroup } = useStudents();
const { t } = useI18n();
watch(
() => props.group,
async (newGroup) => {
await getStudentsByGroup(newGroup.id);
},
{ deep: true, immediate: true },
);
/**
* Leaves the selected group.
*/
Expand All @@ -38,20 +29,19 @@ async function leaveSelectedGroup(): Promise<void> {
emit('group-left', null);
}
}
onMounted(async () => {
await getStudentsByGroup(props.group.id);
});
</script>

<template>
<div class="groupcard">
<h2>{{ t('views.projects.groupMembers') }}</h2>
<div class="mt-4">
<p v-for="student in students" :key="student.id">{{ student.first_name }} {{ student.last_name }}</p>
<div class="p-4 surface-50">
<h2 class="mt-0">
{{ t('views.projects.groupMembers') }}
</h2>
<div class="flex flex-column gap-3 my-4">
<div class="flex align-items-center gap-2" v-for="student in group.students" :key="student.id">
<i class="pi pi-user" /> <span>{{ student.getFullName() }}</span>
</div>
</div>
<Button
class="mt-3"
@click="leaveSelectedGroup"
:icon="PrimeIcons.ARROW_RIGHT"
:label="t('views.projects.leaveGroup')"
Expand All @@ -61,31 +51,4 @@ onMounted(async () => {
</div>
</template>

<style lang="scss">
@import '@/assets/scss/theme/theme.scss';
.groupcard {
background-color: white;
border-radius: $borderRadius;
padding: $cardBodyPadding;
border-style: solid;
border-color: $primaryLightColor;
border-width: $borderWidth;
color: $primaryColor;
div {
p {
color: $textSecondaryColor;
padding-bottom: 1rem;
margin-bottom: 1rem;
border-bottom: 1px solid #e0e0e0;
}
/* Zorgt ervoor dat er geen lijn onder de laatste naam staat */
p:last-child {
border-bottom: none;
padding-bottom: 0;
}
}
}
</style>
<style lang="scss"></style>
Loading

0 comments on commit 1ed52e7

Please sign in to comment.