Skip to content
Draft
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
257 changes: 257 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"vuetify": "^2.2.11",
"vuex": "^3.4.0",
"vuex-persistedstate": "^3.1.0",
"xlsx": "^0.16.9",
"zingtouch": "^1.0.6"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions src/components/Cards/CourseCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<v-chip
color="light-green"
label
x-small
text-color="white"
class="chip"
v-bind="attrs"
Expand Down
4 changes: 2 additions & 2 deletions src/components/CourseSelectionPage/CoursePlan.vue
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,11 @@ export default {
this.sortRequirements();
},
change(event) {
//we only check add events
if (!event.added) return;
this.updateCacheTime();
this.validateCourses();
this.updateChecklist();
//we only check add events
if (!event.added) return;
let changedReq = event.added.element;
if (changedReq.number_of_courses > 1 && changedReq.inRequirementBar) {
changedReq.number_of_courses = 1;
Expand Down
8 changes: 6 additions & 2 deletions src/components/Modals/ProgramSelectionModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export default {
})
.then(response => {
this.selectedMajor = major;
this.selectedSpec = "";
this.selectedSpec = this.noProgram;
this.selectedMinors = [];
this.newMinorsList = response.data["minor_list"].map(minor => {
return new ProgramInfo(minor);
Expand Down Expand Up @@ -223,6 +223,11 @@ export default {
);
}
},
findOptionByProgram: function(program) {
return this.newSpecList.find(obj => {
return program === obj.program_name;
});
},
confirmSelection: function() {
let changeMajor =
this.selectedMajor !== this.noProgram &&
Expand Down Expand Up @@ -328,7 +333,6 @@ export default {
"allSpecializations",
"findMajorByProgram",
"findMinorByProgram",
"findOptionByProgram",
"majorRequirements",
"minorRequirements",
"specRequirements"
Expand Down
145 changes: 114 additions & 31 deletions src/components/UploadPage/UploadPlanPage.vue
Original file line number Diff line number Diff line change
@@ -1,40 +1,123 @@
<template>
<div>
<v-file-input
chips
v-on:change="validateFile"
v-model="selectedFile"
>
</v-file-input>
<v-btn v-on:click="uploadCSV">
Import Plan
</v-btn>
</div>
<div class="upload-page-container">
<h2>Plan Upload</h2>
<div class="upload-page-body">
<p>
UW Path saves your work for you within your browser, so when you come
back to the site your plan will still be here.
</p>
<p>
However, you may also want to download your plan to a spreadsheet file
(via the download icon in the left menu) for more permanent storage,
since clearing your browser's data will cause your work on UW Path to be
lost.
</p>
<p>
If that happens, then you can upload your spreadsheet file below to
repopulate your course plan!
</p>
<p>
This should save you from having to do the same work twice for now, but
getting user accounts up and running is our next top priority so that
even this will be unnecessary.
</p>
</div>
<div class="upload-page-inputs">
<v-file-input
chips
v-model="selectedFile"
placeholder="Select a CSV file"
accept="text/csv"
>
</v-file-input>
<v-btn class="import-button" v-on:click="handleFile">
Import Plan
</v-btn>
</div>
</div>
</template>
<script>
import csv from 'csvtojson';
import XLSX from "xlsx";
import { CourseRequirement } from "../../models/courseRequirementModel";
import { mapActions } from "vuex";

export default {
name: "UploadPlanPage",
data() {
return {
selectedFile: undefined,
};
export default {
name: "UploadPlanPage",
data() {
return {
selectedFile: undefined
};
},
methods: {
...mapActions(["addTableRequirements"]),
parseGroup(code) {
let group = "";
if (
code === "SCIENCE" ||
code === "MATH" ||
code === "LANGUAGE" ||
code === "NON-MATH" ||
(code !== "Program Elective" && code.includes("Elective"))
)
group = code;
return group;
},
methods: {
validateFile(e) {
this.selectedFile = e;
},
uploadCSV() {
csv()
.fromStream(this.selectedFile)
.then(planJson => {
console.log(planJson);
handleFile() {
var reader = new FileReader();
reader.onload = e => {
// Open and parse worksheet
var data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: "array" });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const parsedWorksheet = XLSX.utils.sheet_to_json(worksheet, {
header: 1
});
},
},
};

// Create course requirments to add to table
let tableAdditions = [];
for (let c = 0; c < parsedWorksheet[1].length; c++) {
let term = [];
for (let r = 1; r < parsedWorksheet.length; r++) {
if (parsedWorksheet[r][c]) {
let code = parsedWorksheet[r][c].toUpperCase();
if (code.startsWith("1 OF")) {
code = code.slice(5);
}
let parsed_requirement = {
course_codes_raw: code,
number_of_courses: 1,
inRequirementBar: false,
group: this.parseGroup(code)
};
let parsed_req_obj = new CourseRequirement(parsed_requirement);
term.push(parsed_req_obj);
}
}
tableAdditions.push(term);
}

// Add Courses To Table
this.addTableRequirements({ tableAdditions });
};
reader.readAsArrayBuffer(this.selectedFile);
}
}
};
</script>
<style scoped>

.upload-page-container {
text-align: left;
padding: 2%;
}
.upload-page-body {
text-align: left;
margin-top: 1%;
}
.upload-page-inputs {
display: flex;
align-items: center;
}
.import-button {
margin-left: 2%;
}
</style>
20 changes: 17 additions & 3 deletions src/store/modules/courseSelection.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,8 @@ const state = {
checklistMajorRequirements: {},
checklistMinorRequirements: {},
checklistOptionRequirements: {},
cacheTime: 0
cacheTime: 0,
needsRefresh: false
};

const getters = {
Expand All @@ -312,7 +313,8 @@ const getters = {
getCourse: state => (termIndex, courseIndex) => {
return state.table[termIndex].courses[courseIndex];
},
cacheTime: state => state.cacheTime
cacheTime: state => state.cacheTime,
needsRefresh: state => state.needsRefresh
};

function getRequirementFulfillmentSize(req_course_codes) {
Expand Down Expand Up @@ -794,7 +796,7 @@ function getCoursesTable(state) {
let t = [];
for (let j = 0; j < state.table[i].courses.length; j++) {
let courses = [];
courses.push(state.table[i].courses[j].course_codes_raw);
courses.push("1 of " + state.table[i].courses[j].course_codes_raw);
if (!state.table[i].courses[j].selected_course) {
courses.push("WAITING");
} else {
Expand Down Expand Up @@ -1112,6 +1114,9 @@ const actions = {
} else {
commit("setChecklistOptionRequirements", {});
}
},
addTableRequirements: ({ commit }, { tableAdditions }) => {
commit("addTableRequirements", { tableAdditions });
}
};

Expand All @@ -1121,6 +1126,15 @@ const mutations = {
termIndex
].courses[courseIndex].overridden;
},
setNeedsRefresh: (state, { needsRefresh }) => {
state.needsRefresh = needsRefresh;
},
addTableRequirements: (state, { tableAdditions }) => {
state.needsRefresh = true;
for (let i = 0; i < tableAdditions.length; i++) {
state.table[i].courses = state.table[i].courses.concat(tableAdditions[i]);
}
},
addChecklistRequirement: (
state,
{ newRequirement, program, programType }
Expand Down
54 changes: 51 additions & 3 deletions src/views/CourseSelectionPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@
<span>Download plan to CSV</span>
</v-tooltip>

<v-tooltip right open-delay="300" max-width="250px">
<template v-slot:activator="{ on, attrs }">
<v-btn
class="tab-icon tab-button"
v-bind="attrs"
v-on="on"
@click="scrollUpload()"
>
<v-icon x-large>mdi-upload-outline</v-icon>
</v-btn>
</template>
<span>Upload plan from spreadsheet</span>
</v-tooltip>

<v-spacer></v-spacer>

<v-tooltip right open-delay="300" max-width="250px">
Expand Down Expand Up @@ -78,11 +92,16 @@
<course-plan :allCourses="allCourses" />
</v-col>
</v-row>
<v-row v-show="!inTable" class="main-row" id="checklist-row">
<v-row v-show="inChecklist" class="main-row" id="checklist-row">
<v-col class="main-panel">
<program-checklist />
</v-col>
</v-row>
<v-row v-show="inUpload" class="main-row" id="checklist-row">
<v-col class="main-panel">
<upload-plan-page />
</v-col>
</v-row>
</v-tab-item>
</v-tabs>
<v-dialog v-model="openBugModal" max-width="800">
Expand All @@ -95,13 +114,14 @@
import CoursePlan from "../components/CourseSelectionPage/CoursePlan.vue";
import ProgramSelectionBar from "../components/CourseSelectionPage/ProgramSelectionBar.vue";
import ProgramChecklist from "../components/ProgramChecklistPage/ProgramChecklist.vue";
import UploadPlanPage from "../components/UploadPage/UploadPlanPage.vue";
import TroubleShootModalContent from "../components/Modals/TroubleShootModalContent.vue";
import { CourseInfo } from "../models/courseInfoModel";
import SideBar from "../components/CourseSelectionPage/SideBar.vue";
import TrieSearch from "trie-search";
import axios from "axios";
import { backend_api } from "../backendAPI";
import { mapActions } from "vuex";
import { mapActions, mapMutations, mapGetters } from "vuex";

export default {
name: "CourseSelection",
Expand All @@ -110,19 +130,26 @@ export default {
ProgramSelectionBar,
SideBar,
ProgramChecklist,
UploadPlanPage,
TroubleShootModalContent
},
data: () => ({
inTable: true,
inChecklist: false,
inUpload: false,
openBugModal: false,
allCourses: new TrieSearch(["course_code", "course_number"], {
idFieldOrFunction: function(course) {
return course.course_id + course.course_code;
}
})
}),
computed: {
...mapGetters(["needsRefresh"])
},
methods: {
...mapActions(["export"]),
...mapMutations(["validateCourses", "setNeedsRefresh"]),
...mapActions(["export", "updateChecklist"]),
exportPDF() {
this.export({ PDF: true, XLS: false });
},
Expand All @@ -134,9 +161,30 @@ export default {
},
scrollTable() {
this.inTable = true;
this.inChecklist = false;
this.inUpload = false;
// Update checklist and validate courses
if (this.needsRefresh) {
this.updateChecklist();
this.validateCourses();
this.setNeedsRefresh({ needsRefresh: false });
}
},
scrollChecklist() {
this.inTable = false;
this.inChecklist = true;
this.inUpload = false;
// Update checklist and validate courses
if (this.needsRefresh) {
this.updateChecklist();
this.validateCourses();
this.setNeedsRefresh({ needsRefresh: false });
}
},
scrollUpload() {
this.inTable = false;
this.inChecklist = false;
this.inUpload = true;
},
openBugTroubleshootModal() {
this.openBugModal = true;
Expand Down