Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better academic year selector, filtering and sorting #292

Merged
merged 2 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions backend/api/management/commands/seed_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,26 +491,26 @@ def seed_data(self, amount, provider_function, update_function):
def handle(self, *args, **options):
start_time = time.time()
# TODO maybey take as option
# amount_of_students = 50_000
# amount_of_assistants = 300
# amount_of_teachers = 500
# amount_of_courses = 1_000
# amount_of_projects = 3_000
# amount_of_groups = 9_000
# amount_of_submissions = 50_000
# amount_of_file_extensions = 20
# amount_of_structure_checks = 12_000

amount_of_students = 10
amount_of_assistants = 0
amount_of_teachers = 0
amount_of_courses = 0
amount_of_projects = 0
amount_of_groups = 0
amount_of_submissions = 0
amount_of_students = 50_000
amount_of_assistants = 5_000
amount_of_teachers = 1_500
amount_of_courses = 1_500
amount_of_projects = 3_000
amount_of_groups = 3_000
amount_of_submissions = 3_000
amount_of_file_extensions = 0
amount_of_structure_checks = 0

# amount_of_students = 10
# amount_of_assistants = 0
# amount_of_teachers = 0
# amount_of_courses = 0
# amount_of_projects = 0
# amount_of_groups = 0
# amount_of_submissions = 0
# amount_of_file_extensions = 0
# amount_of_structure_checks = 0

self.seed_data(amount_of_students, fake.provide_student, update_Student_providers)
self.stdout.write(self.style.SUCCESS('Successfully seeded students!'))
self.seed_data(amount_of_assistants, fake.provide_assistant, update_Assistant_providers)
Expand Down
8 changes: 5 additions & 3 deletions backend/api/views/course_view.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from django.db.models import Min, Max

from api.models.course import Course
from api.permissions.course_permissions import (CourseAssistantPermission,
CoursePermission,
Expand Down Expand Up @@ -26,6 +28,8 @@
from rest_framework.request import Request
from rest_framework.response import Response

from api.views.pagination.course_pagination import CoursePagination


class CourseViewSet(viewsets.ModelViewSet):
"""Actions for general course logic"""
Expand All @@ -47,10 +51,8 @@ def create(self, request: Request, *_):

return Response(serializer.data, status=status.HTTP_201_CREATED)

@action(detail=False)
@action(detail=False, pagination_class=CoursePagination)
def search(self, request: Request) -> Response:
self.pagination_class = BasicPagination

# Extract filter params
search = request.query_params.get("search", "")
years = request.query_params.getlist("years[]")
Expand Down
21 changes: 21 additions & 0 deletions backend/api/views/pagination/course_pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.db.models import Min, Max
from rest_framework.response import Response
from api.models.course import Course
from api.views.pagination.basic_pagination import BasicPagination


class CoursePagination(BasicPagination):
def get_paginated_response(self, schema):
# Get min and max years
years = Course.objects.all().aggregate(
Min('academic_startyear'), Max('academic_startyear')
)

return Response({
'count': self.page.paginator.count,
'next': self.get_next_link(),
'previous': self.get_previous_link(),
'min_year': years['academic_startyear__min'],
'max_year': years['academic_startyear__max'],
'results': schema,
})
16 changes: 12 additions & 4 deletions frontend/src/components/projects/ProjectList.vue
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
<script setup lang="ts">
import { type Course } from '@/types/Course.ts';
import { computed, watch } from 'vue';
import { computed, ref, watch } from 'vue';
import { useProject } from '@/composables/services/project.service.ts';
import ProjectCard from '@/components/projects/ProjectCard.vue';
import { useI18n } from 'vue-i18n';
import moment from 'moment';
import Skeleton from 'primevue/skeleton';
import InputSwitch from 'primevue/inputswitch';

/* Props */
const props = withDefaults(
defineProps<{
courses: Course[] | null;
showPast?: boolean;
cols?: number;
}>(),
{
courses: () => [],
showPast: false,
cols: 4,
},
);
Expand All @@ -26,6 +25,7 @@ const { t } = useI18n();
const { projects, getProjectsByCourse } = useProject();

/* State */
const showPast = ref(false);

// The merged projects from all courses
const allProjects = computed(() => props.courses?.flatMap((course) => course.projects) ?? null);
Expand All @@ -35,7 +35,7 @@ const allProjects = computed(() => props.courses?.flatMap((course) => course.pro
*/
const sortedProjects = computed(() => {
const projects =
allProjects.value?.filter((project) => (!props.showPast ? moment(project.deadline).isAfter() : true)) ?? null;
allProjects.value?.filter((project) => (!showPast.value ? moment(project.deadline).isAfter() : true)) ?? null;

if (projects === null) {
return projects;
Expand Down Expand Up @@ -70,6 +70,14 @@ watch(
</script>

<template>
<!-- Show past projects switch -->
<div class="flex gap-3 align-items-center mb-5">
<InputSwitch input-id="show-past" v-model="showPast" />
<label for="show-past">
{{ t('views.dashboard.showPastProjects') }}
</label>
</div>
<!-- Project list -->
<div class="grid align-items-stretch">
<template v-if="sortedProjects !== null">
<template v-if="sortedProjects.length > 0">
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/composables/filters/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useRoute, useRouter } from 'vue-router';

export interface FilterState {
filter: Ref<Filter>;
onFilter: (callback: () => Promise<void>) => void;
onFilter: (callback: () => Promise<void>, debounce?: number, immediate?: boolean) => void;
}

export function useFilter(initial: Filter): FilterState {
Expand All @@ -18,8 +18,10 @@ export function useFilter(initial: Filter): FilterState {
* On filter callback
*
* @param callback
* @param debounce
* @param immediate
*/
function onFilter(callback: () => Promise<void>): void {
function onFilter(callback: () => Promise<void>, debounce: number = 500, immediate: boolean = true): void {
watchDebounced(
filter,
async () => {
Expand All @@ -32,7 +34,7 @@ export function useFilter(initial: Filter): FilterState {

await callback();
},
{ debounce: 500, immediate: true, deep: true },
{ debounce, immediate, deep: true },
);
}

Expand Down
51 changes: 22 additions & 29 deletions frontend/src/composables/filters/paginator.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { computed, type Ref, ref, watch } from 'vue';
import { type LocationQuery, useRoute, useRouter } from 'vue-router';
import { useRoute, useRouter } from 'vue-router';
import { type PaginatorResponse } from '@/types/filter/Paginator.ts';

export interface PaginatorState {
page: Ref<number>;
pageSize: Ref<number>;
first: Ref<number>;
paginate: (newFirst: number) => Promise<void>;
onPaginate: (callback: () => Promise<void>) => void;
resetPagination: (pagination: Ref<PaginatorResponse<any> | null>) => Promise<void>;
}

export function usePaginator(initialPage: number = 1, initialPageSize: number = 20): PaginatorState {
Expand All @@ -15,23 +16,9 @@ export function usePaginator(initialPage: number = 1, initialPageSize: number =
const { push } = useRouter();

/* State */
const page = ref<number>(initialPage);
const pageSize = ref<number>(initialPageSize);
const page = ref<number>(parseInt(query.page?.toString() ?? initialPage.toString()));

/* Watchers */
watch(
() => query,
(query: LocationQuery) => {
if (query.page !== undefined) {
page.value = parseInt(query.page as string);
}

if (query.pageSize !== undefined) {
pageSize.value = parseInt(query.pageSize as string);
}
},
{ immediate: true },
);
const pageSize = ref<number>(parseInt(query.pageSize?.toString() ?? initialPageSize.toString()));

/* Computed */
const first = computed({
Expand All @@ -40,19 +27,18 @@ export function usePaginator(initialPage: number = 1, initialPageSize: number =
});

/**
* Paginate using a new first item index.
* Reset the pagination to the first page.
*
* @param newFirst
* @returns void
*/
async function paginate(newFirst: number): Promise<void> {
first.value = newFirst;
async function resetPagination(pagination: Ref<PaginatorResponse<any> | null>): Promise<void> {
// Paginate to the first page upon filter change
first.value = 0;

await push({
query: {
page: page.value,
pageSize: pageSize.value,
},
});
if (pagination.value !== null) {
// Reset the results
pagination.value.results = null;
}
}

/**
Expand All @@ -62,6 +48,13 @@ export function usePaginator(initialPage: number = 1, initialPageSize: number =
*/
function onPaginate(callback: () => Promise<void>): void {
watch(page, async () => {
await push({
query: {
page: page.value,
pageSize: pageSize.value,
},
});

await callback();
});
}
Expand All @@ -70,7 +63,7 @@ export function usePaginator(initialPage: number = 1, initialPageSize: number =
page,
pageSize,
first,
paginate,
onPaginate,
resetPagination,
};
}
6 changes: 3 additions & 3 deletions frontend/src/composables/services/courses.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { Course } from '@/types/Course.ts';
import { type Ref, ref } from 'vue';
import { endpoints } from '@/config/endpoints.ts';
import { get, getList, create, deleteId, getPaginatedList } from '@/composables/services/helpers.ts';
import { type PaginatorResponse } from '@/types/filter/Paginator.ts';
import { type CoursePaginatorResponse } from '@/types/filter/Paginator.ts';
import { type Filter } from '@/types/filter/Filter.ts';

interface CoursesState {
pagination: Ref<PaginatorResponse<Course> | null>;
pagination: Ref<CoursePaginatorResponse | null>;
courses: Ref<Course[] | null>;
course: Ref<Course | null>;
getCourseByID: (id: string) => Promise<void>;
Expand All @@ -21,7 +21,7 @@ interface CoursesState {
}

export function useCourses(): CoursesState {
const pagination = ref<PaginatorResponse<Course> | null>(null);
const pagination = ref<CoursePaginatorResponse | null>(null);
const courses = ref<Course[] | null>(null);
const course = ref<Course | null>(null);

Expand Down
29 changes: 29 additions & 0 deletions frontend/src/types/Course.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,32 @@ export class Course {
);
}
}

/**
* Get the academic year of a date.
*
* @param date
* @returns number
*/
export function getAcademicYear(date: Date = new Date()): number {
const year = date.getFullYear();

if (date.getMonth() >= 9) {
return year;
}

return year - 1;
}

/**
* Get the academic years between a minimum and maximum.
*
* @param years
* @returns number[]
*/
export function getAcademicYears(...years: number[]): number[] {
const min = Math.min(...years);
const max = Math.max(...years);

return Array.from({ length: max - min + 1 }, (_, i) => max - i);
}
9 changes: 8 additions & 1 deletion frontend/src/types/filter/Paginator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { type Course } from '@/types/Course.ts';

export interface PaginatorResponse<T> {
count: number;
next: string | null;
previous: string | null;
results: T[];
results: T[] | null;
}

export interface CoursePaginatorResponse extends PaginatorResponse<Course> {
min_year: number;
max_year: number;
}
26 changes: 0 additions & 26 deletions frontend/src/types/users/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,6 @@ export class User {
public last_login: Date | null,
) {}

/**
* Get the academic years of the user.
*/
get academic_years(): number[] {
const startYear = User.getAcademicYear(this.create_time);
const endYear = User.getAcademicYear(new Date());

return Array.from({ length: endYear - startYear + 1 }, (_, i) => startYear + i);
}

/**
* Get the full name of the user.
*
Expand All @@ -36,22 +26,6 @@ export class User {
return `${this.first_name} ${this.last_name}`;
}

/**
* Get the academic year of a date.
*
* @param date
* @returns number
*/
public static getAcademicYear(date: Date = new Date()): number {
const year = date.getFullYear();

if (date.getMonth() >= 9) {
return year;
}

return year - 1;
}

/**
* Check if the user is a student.
*
Expand Down
Loading
Loading