Skip to content

Commit

Permalink
Import from INBUS rewrite in Vue
Browse files Browse the repository at this point in the history
  • Loading branch information
geordi committed Oct 9, 2024
1 parent 1a41ecb commit c7078e9
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 2 deletions.
266 changes: 266 additions & 0 deletions frontend/src/AppInbusImport.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
<script setup lang="ts">
import {ref, watch, onMounted} from 'vue';
import {csrfToken} from './api.js'
let subject_inbus_selected = ref<number[]|null>(null);
let subject_kelvin_selected = ref(null);
let semester = ref(null);
let busy = ref<boolean>(false);
let semesters = ref(null);
let semesters_loading = ref<boolean>(true);
let inbus_and_kelvin_subjects_loading = ref<boolean>(true);
let inbus_schedule_loaded = ref<boolean>(false);
let subjects_inbus : InbusSubjectVersion[] | null = null;
let subjects_inbus_filtered : InbusSubjectVersion[] | null= null;
let subjects_kelvin : KelvinSubject[] | null = null;
let subject_inbus_schedule = ref<ConcreteActivity[]|null>(null);
let classes_to_import = ref([]);
let result = ref(null);
let canImport = ref(classes_to_import.value.length && !busy.value);
function assembleRequest(url: string) : RequestInfo {
const headers: Headers = new Headers();
headers.set('Content-Type', 'application/json');
headers.set('Accept', 'application/json');
const request: RequestInfo = new Request(url, {
method: 'GET',
headers: headers
});
return request;
}
function svcc2num(svcc: string) : number {
const [dept_code, version] : [string, string] = svcc.split('/');
const [dept, code] : [string, string] = dept_code.split('-');
const svcc_str = dept + code + version;
const svcc_num = Number(svcc_str);
return svcc_num;
}
class InbusGuarantee {
personId: number
login: string
fullName: string
}
class InbusSubject {
subjectId: number
code: string
abbrev: string
title: string
guarantee: InbusGuarantee
}
class InbusSubjectVersion {
subjectVersionId: number
subject: InbusSubject
subjectVersionCompleteCode: string
guarantee: InbusGuarantee
}
class KelvinSubject {
name: string
abbr: string
}
async function loadInbusAndKelvinSubjects() {
const res1 = await fetch('/api/inbus/subject_versions', {});
const res2 = await fetch('/api/subjects/all', {});
subjects_inbus = await res1.json();
const subjects_kelvin_resp = await res2.json();
subjects_kelvin = subjects_kelvin_resp.subjects;
subjects_kelvin.sort((a, b) => {
const name_a = a.name.toUpperCase();
const name_b = b.name.toUpperCase();
if (name_a < name_b) {
return -1;
}
if (name_a > name_b) {
return 1;
}
return 0;
});
const subject_kelvin_abbrs = subjects_kelvin.map(s => s.abbr);
subjects_inbus_filtered = subjects_inbus.filter(subject_inbus => subject_kelvin_abbrs.includes(subject_inbus.subject.abbrev));
subjects_inbus_filtered = subjects_inbus_filtered.sort((a, b) =>
svcc2num(a.subjectVersionCompleteCode) - svcc2num(b.subjectVersionCompleteCode));
inbus_and_kelvin_subjects_loading.value = false;
}
class Semester {
pk: number
year: number
winter: boolean
}
function parseSemesters(semesters_data : Semester[]) {
semesters.value = semesters_data.map(sm => ({'pk': sm.pk, 'year': sm.year, 'winter': sm.winter, 'display': String(sm.year) + (sm.winter ? 'W' : 'S')}));
semesters_loading.value = false;
}
async function loadSemesters() {
const request = assembleRequest("/api/semesters")
const res = await fetch(request, {})
const semesters_data = await res.json();
parseSemesters(semesters_data["semesters"]);
}
async function loadScheduleForSubjectVersionId(subject_inbus: InbusSubjectVersion) {
const request = assembleRequest("/api/inbus/schedule/subject/version/" + subject_inbus.subjectVersionId)
let res = await fetch(request, {});
subject_inbus_schedule = await res.json();
inbus_schedule_loaded.value = true;
}
async function importActivities() {
busy.value = true;
const req = {
'semester_id': semester.value,
'subject': subject_kelvin_selected.value,
'activities': classes_to_import.value,
};
console.log(req);
const res = await fetch('/api/import/activities', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken(),
},
body: JSON.stringify(req),
});
result.value = (await res.json());
busy.value = false;
}
onMounted(() => {
watch(subject_inbus_selected, (newValue, oldValue) => {
inbus_schedule_loaded.value = false;
loadScheduleForSubjectVersionId(newValue);
});
watch(classes_to_import, (newValue, oldValue) => {
canImport.value = (classes_to_import.value.length && !busy.value);
});
loadSemesters();
loadInbusAndKelvinSubjects();
})
</script>


<template>
<select v-if="!semesters_loading" v-model="semester">
<option v-for="item in semesters" :key="item.pk" :value="item.pk">{{item.display}}</option>
</select>



<select v-if="!inbus_and_kelvin_subjects_loading" v-model="subject_kelvin_selected">
<option v-for="item in subjects_kelvin" :key="item" :value="item">{{item.abbr}} - {{item.name}}</option>
</select>



<select v-if="!inbus_and_kelvin_subjects_loading" v-model="subject_inbus_selected">
<option v-for="item in subjects_inbus_filtered" :key="item" :value="item">{{item.subjectVersionCompleteCode}} - {{item.subject.abbrev}} - {{item.subject.title}}</option>
</select>

<table v-if="inbus_schedule_loaded" class="table table-hover table-stripped table-sm">
<tbody>
<tr v-for="ca in subject_inbus_schedule">
<td>
<label>
<input type="checkbox" v-model="classes_to_import" :value="ca.concreteActivityId" name="classesToImport"/>
{{ca.educationTypeAbbrev}}
</label>
</td>

<td>
{{ca.educationTypeAbbrev}}/{{ca.order}}, {{ca.subjectVersionCompleteCode}}
</td>

<td>
{{ca.teacherFullNames}}
</td>

<td>
{{ca.weekDayAbbrev}}
</td>

<td>
{{ca.beginTime}}
</td>

<td>
{{ca.endTime}}
</td>
</tr>
</tbody>
</table>


<button class="btn" :class="{ 'btn-success': canImport, 'btn-danger': !canImport }" @click="importActivities" :disabled="!canImport">
<span v-if="busy">Importing...</span>
<span v-else>Import</span>
</button>


<div>
<div v-if="result">
<div v-if="result.error" class="alert alert-danger" role="alert">
{{ result.error }}
</div>
<div v-else>
<table class="table table-sm table-hover table-striped">
<tbody>
<tr v-for="item in result.users" :key="item.login">
<td>{{ item.login }}</td>
<td>{{ item.firstname }}</td>
<td>{{ item.lastname }}</td>
<td>{{ item.created }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>


<style scoped>
</style>
2 changes: 1 addition & 1 deletion frontend/src/ClassList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ $: {
teacher={filter.teacher}
clazz={filter.class} />

<a class="btn btn-sm p-1" href="/#/import" title="Bulk import students from EDISON">
<a class="btn btn-sm p-1" href="/import/inbus" title="Bulk import students from EDISON">
<span class="iconify" data-icon="mdi:calendar-import"></span>
</a>

Expand Down
8 changes: 8 additions & 0 deletions frontend/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,18 @@ window.addEventListener('DOMContentLoaded', focusTab);

import { defineCustomElement } from 'vue';
import Example from './ExampleComponent.vue';
import AppInbusImport from "./AppInbusImport.vue";

customElements.define(
'kelvin-example',
defineCustomElement(Example, {
shadowRoot: false // https://github.com/vuejs/core/issues/4314#issuecomment-2266382877
})
);

customElements.define(
'kelvin-import-inbus',
defineCustomElement(AppInbusImport, {
shadowRoot: false // https://github.com/vuejs/core/issues/4314#issuecomment-2266382877
})
);
5 changes: 5 additions & 0 deletions templates/web/inbusimport.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{% extends 'web/layout.html' %}

{% block fullcontent %}
<kelvin-import-inbus></kelvin-import-inbus>
{% endblock %}
1 change: 1 addition & 0 deletions web/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

urlpatterns = [
path("", common_view.index, name="index"),
path("import/inbus", common_view.import_inbus, name="import_inbus"),
path("student-view", student_view.student_index, name="student_index"),
path(
"find-task/<int:task_id>/<str:login>/",
Expand Down
7 changes: 6 additions & 1 deletion web/views/common.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import login_required, user_passes_test
from django.shortcuts import render
from django.utils.crypto import get_random_string
from django.conf import settings
Expand All @@ -17,6 +17,11 @@ def index(request):
return student_index(request)


@user_passes_test(is_teacher)
def import_inbus(request):
return render(request, 'web/inbusimport.html', {})


@login_required()
def api_token(request):
data = {
Expand Down

0 comments on commit c7078e9

Please sign in to comment.