diff --git a/backend/package.json b/backend/package.json index dd63f3333b6d50735f4759f41e4aa5b7dace0bdd..58bc2d786f79aa49a2f4bcf2b37344991bafc2ce 100644 --- a/backend/package.json +++ b/backend/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "test": "NODE_ENV=test && ./node_modules/mocha/bin/mocha src/test", - "start": "NODE_ENV=development && node src/index.js" + "start": "node src/index.js" }, "author": "Group B", "license": "MIT", diff --git a/frontend/src/App.scss b/frontend/src/App.scss index 26a8848b06922115f35664de3ca83b63f8db7cba..3af9143cb8d84d6e65305280809d12a9c196364a 100644 --- a/frontend/src/App.scss +++ b/frontend/src/App.scss @@ -329,6 +329,7 @@ tr:hover { .cv-item { color: #fff; + text-align: left; background-color: transparent; background-image: linear-gradient(to bottom right, #1673b1, #4fa8f0); border-radius: 3px !important; @@ -363,6 +364,21 @@ tr:hover { .cv-day.do-you-remember.the-21st .cv-day-number::after { content: "\1F525"; } + + .cv-weeks { + .cv-weekdays { + div.offset1.span1.cv-item { + // top: calc(3.4em + 0px) !important; + + &:nth-child(1) { + top: calc(3.4em + 0px) !important; + } + &:nth-child(2) { + top: calc(3.4em + 0px) !important; + } + } + } + } } /* The following classes style the classes computed in myDateClasses and passed to the component's dateClasses prop. */ diff --git a/frontend/src/components/StudentSchedule/StudentSchedule.vue b/frontend/src/components/StudentSchedule/StudentSchedule.vue index 57fd10a80fb489e25dea230f55ddce5423444d8a..83d13a196f4521a0a1e7a222da6bdd39436ad338 100644 --- a/frontend/src/components/StudentSchedule/StudentSchedule.vue +++ b/frontend/src/components/StudentSchedule/StudentSchedule.vue @@ -32,14 +32,20 @@ </tr> </thead> <tbody> - <tr> - <td>1</td> - <td>IF5122</td> - <td>Proyek Perangkat Lunak</td> + <tr + v-for="(course, index) in studentCourseData" + :key="course.id" + scope="row" + > + <td>{{ index + 1 }}</td> + <td>{{ course.code }}</td> + <td>{{ course.name }}</td> <td class="text-center">01</td> - <td class="text-center">2</td> - <td class="text-center">WAJIB</td> - <td class="text-center">100%</td> + <td class="text-center">{{ course.credits }}</td> + <td class="text-center">{{ course.type }}</td> + <td class="text-center"> + {{ course.attendancePercentage }}% + </td> </tr> </tbody> </table> @@ -126,7 +132,7 @@ <div class="modal-content p-3"> <div class="modal-header"> <h2 class="mx-auto text-blue-six" id="infoLecture"> - [Code][Nama Mata Kuliah] + {{ classModalData.courseCode }} {{ classModalData.courseName }} </h2> </div> <div class="modal-body overflow-auto"> @@ -135,27 +141,33 @@ <!-- Field Lecture Schedule --> <div class="col-12"> <label><b>Kuliah Online / E-Learning</b></label> - <p>Selasa / 16 Mar 2021 / 13:00 - 15:00</p> + <p> + {{ classModalData.day }} / {{ classModalData.date }} / + {{ classModalData.startTime }} - + {{ classModalData.endTime }} + </p> </div> <!-- Field Lecturer --> <div class="col-12"> <label><b>Dosen</b></label> - <p>Dr.techn. Wikan Danar Sunindyo, S.T., M.Sc.</p> + <p>{{ classModalData.lecturer }}</p> </div> <!-- Field Lecture Link --> <div class="col-12"> <label><b>Link</b></label> - <p><a href="#">https://meet.google.com/fop-cqmf-ggw</a></p> + <p> + <a href="#">{{ classModalData.link }}</a> + </p> </div> <!-- Field Lecture Topic --> <div class="col-12"> <label><b>Topik</b></label> - <p>Manajemen Cost</p> + <p>{{ classModalData.topic }}</p> </div> <!-- Field Notes --> <div class="col-12"> <label><b>Catatan</b></label> - <p>-</p> + <p>{{ classModalData.notes }}</p> </div> </div> <div class="row mt-5"> @@ -179,6 +191,7 @@ </template> <script> +import axios from "axios"; import { Modal } from "bootstrap"; // Load CSS from the published version import "../../../node_modules/vue-simple-calendar/dist/style.css"; @@ -206,6 +219,19 @@ export default { year: 2021, /* Show the current month, and give it some fake items to show */ showDate: this.thisMonth(1), + studentCourseData: [], + classModalData: { + courseCode: null, + courseName: null, + day: null, + date: null, + startTime: null, + endTime: null, + lecturer: null, + link: null, + topic: null, + notes: null + }, message: "", lectureInfoModal: null, startingDayOfWeek: 1, @@ -223,79 +249,7 @@ export default { useDefaultTheme: true, useHolidayTheme: true, useTodayIcons: false, - items: [ - { - id: "e0", - startDate: "2018-01-05" - }, - { - id: "e1", - startDate: this.thisMonth(15, 18, 30) - }, - { - id: "e2", - startDate: this.thisMonth(15), - title: "Single-day item with a long title" - }, - { - id: "e3", - startDate: this.thisMonth(7, 9, 25), - endDate: this.thisMonth(10, 16, 30), - title: "Multi-day item with a long title and times" - }, - { - id: "e4", - startDate: this.thisMonth(20), - title: "My Birthday!", - classes: "birthday", - url: "https://en.wikipedia.org/wiki/Birthday" - }, - { - id: "e5", - startDate: this.thisMonth(5), - endDate: this.thisMonth(12), - title: "Multi-day item", - classes: "purple" - }, - { - id: "foo", - startDate: this.thisMonth(29), - title: "Same day 1" - }, - { - id: "e6", - startDate: this.thisMonth(29), - title: "Same day 2", - classes: "orange" - }, - { - id: "e7", - startDate: this.thisMonth(29), - title: "Same day 3" - }, - { - id: "e8", - startDate: this.thisMonth(29), - title: "Same day 4", - classes: "orange" - }, - { - id: "e9", - startDate: this.thisMonth(29), - title: "Same day 5" - }, - { - id: "e10", - startDate: this.thisMonth(29), - title: "Same day 6", - classes: "orange" - }, - { - id: "e11", - startDate: this.thisMonth(29), - title: "Same day 7" - } - ] + items: [] }; }, computed: { @@ -307,9 +261,7 @@ export default { }, themeClasses() { return { - "theme-default": this.useDefaultTheme, - "holiday-us-traditional": this.useHolidayTheme, - "holiday-us-official": this.useHolidayTheme + "theme-default": this.useDefaultTheme }; }, myDateClasses() { @@ -323,16 +275,15 @@ export default { const ides = [2, 4, 6, 9].includes(theFirst.getMonth()) ? 15 : 13; const idesDate = this.thisMonth(ides); o[CalendarMath.isoYearMonthDay(idesDate)] = "ides"; - o[CalendarMath.isoYearMonthDay(this.thisMonth(21))] = [ - "do-you-remember", - "the-21st" - ]; + o[CalendarMath.isoYearMonthDay(this.thisMonth(21))] = ["do-you-remember"]; return o; } }, - mounted() { + async mounted() { this.newItemStartDate = CalendarMath.isoYearMonthDay(CalendarMath.today()); this.newItemEndDate = CalendarMath.isoYearMonthDay(CalendarMath.today()); + await this.getStudyPlan(); + await this.getCourseClassMeeting(); }, methods: { openInfoLectureModal() { @@ -366,8 +317,15 @@ export default { this.selectionEnd = null; this.message = `You clicked: ${d.toLocaleDateString()}`; }, - onClickItem(e) { + async onClickItem(e) { + this.classModalData = {}; this.message = `You clicked: ${e.title}`; + console.log("PIJET COK"); + await this.getClassDetail( + e.id, + e.originalItem.courseCode, + e.originalItem.courseName + ); this.openInfoLectureModal(); }, setShowDate(d) { @@ -415,6 +373,240 @@ export default { "/" + (this.year + 1) ); + }, + async getStudyPlan() { + await axios + .get( + process.env.VUE_APP_API_ENDPOINT + + "/student/" + + localStorage.getItem("uid") + ) + .then((response) => { + if (response.status == 200) { + let params = + "studentId=" + + response.data.id + + "&semester=" + + this.semester + + "&startYear=" + + this.year; + + axios + .get(process.env.VUE_APP_API_ENDPOINT + "/study-plan/?" + params) + .then((childResponse) => { + if (childResponse.status == 200) { + this.studentCourseData = childResponse.data.studyPlanCourses; + this.getCourseClass(); + } + }) + .catch((error) => { + console.error(error); + }); + } + }) + .catch((error) => { + console.error(error); + }); + }, + async getCourseClass() { + for (let i = 0; i < this.studentCourseData.length; i++) { + await axios + .get( + process.env.VUE_APP_API_ENDPOINT + + "/course-class/" + + this.studentCourseData[i].courseClassId + ) + .then((response) => { + if (response.status == 200) { + axios + .get( + process.env.VUE_APP_API_ENDPOINT + + "/course/" + + response.data.courseId + ) + .then((responseChild) => { + this.studentCourseData[i].code = responseChild.data.code; + this.studentCourseData[i].name = responseChild.data.name; + this.studentCourseData[i].credits = + responseChild.data.credits; + this.studentCourseData[i].type = responseChild.data.type; + }) + .catch((error) => { + console.error(error); + }); + } + }) + .catch((error) => { + console.error(error); + }); + } + }, + async getCourseClassMeeting() { + await axios + .get( + process.env.VUE_APP_API_ENDPOINT + + "/student/" + + localStorage.getItem("uid") + ) + .then((response) => { + if (response.status == 200) { + let params = + "studentId=" + + response.data.id + + "&semester=" + + this.semester + + "&startYear=" + + this.year; + + axios + .get( + process.env.VUE_APP_API_ENDPOINT + + "/course-class-meeting/?" + + params + ) + .then((childResponse) => { + if (childResponse.status == 200) { + console.log(childResponse.data); + for (let i = 0; i < childResponse.data.length; i++) { + let item = { + id: childResponse.data[i].id, + startDate: childResponse.data[i].startTime, + endDate: childResponse.data[i].endTime, + courseCode: childResponse.data[i].CourseClass.Course.code, + courseName: childResponse.data[i].CourseClass.Course.name, + title: + "<br/>" + + childResponse.data[i].CourseClass.Course.code + + " " + + childResponse.data[i].CourseClass.Course.name + }; + this.items.push(item); + } + } + }) + .catch((error) => { + console.error(error); + }); + } + }) + .catch((error) => { + console.error(error); + }); + }, + async getClassDetail(id, courseCode, courseName) { + await axios + .get( + process.env.VUE_APP_API_ENDPOINT + + "/course-class-meeting/?courseClassMeetingId=" + + id + + "&shouldIncludeAttendances=false" + ) + .then((response) => { + if (response.status == 200) { + let startTime = new Date(response.data.startTime); + let endTime = new Date(response.data.endTime); + this.classModalData.startTime = + (startTime.getHours() - 7).toString() + + ":" + + String(startTime.getMinutes()).padStart(2, "0"); + this.classModalData.endTime = + (endTime.getHours() - 7).toString() + + ":" + + String(endTime.getMinutes()).padStart(2, "0"); + this.classModalData.courseCode = courseCode; + this.classModalData.courseName = courseName; + this.classModalData.link = response.data.link; + this.classModalData.topic = response.data.topic; + this.classModalData.notes = response.data.notes; + this.getLecturerName(response.data.courseClassId); + this.classModalData.day = this.convertDay(endTime.getDay()); + this.classModalData.date = this.convertDate(endTime); + } + }) + .catch((error) => { + console.error(error); + }); + }, + async getLecturerName(courseClassId) { + await axios + .get( + process.env.VUE_APP_API_ENDPOINT + "/course-class/" + courseClassId + ) + .then((response) => { + if (response.status == 200) { + axios + .get(process.env.VUE_APP_API_ENDPOINT + "/lecturer") + .then((childResponse) => { + if (childResponse.status == 200) { + for (let i = 0; i < childResponse.data.length; i++) { + if (childResponse.data[i].id === response.data.lecturerId) { + this.classModalData.lecturer = + childResponse.data[i].fullName; + } + } + } + }) + .catch((error) => { + console.error(error); + }); + } + }) + .catch((error) => { + console.error(error); + }); + }, + convertDay(numDay) { + if (numDay === 0) { + return "Minggu"; + } else if (numDay === 1) { + return "Senin"; + } else if (numDay === 2) { + return "Selasa"; + } else if (numDay === 3) { + return "Rabu"; + } else if (numDay === 4) { + return "Kamis"; + } else if (numDay === 5) { + return "Jumat"; + } else { + return "Sabtu"; + } + }, + convertDate(time) { + let fullDate; + let date = time.getDate().toString(); + let month; + if (time.getMonth() === 0) { + month = "Januari"; + } else if (time.getMonth() === 1) { + month = "Februari"; + } else if (time.getMonth() === 2) { + month = "Maret"; + } else if (time.getMonth() === 3) { + month = "April"; + } else if (time.getMonth() === 4) { + month = "Mei"; + } else if (time.getMonth() === 5) { + month = "Juni"; + } else if (time.getMonth() === 6) { + month = "Juli"; + } else if (time.getMonth() === 7) { + month = "Agustus"; + } else if (time.getMonth() === 8) { + month = "September"; + } else if (time.getMonth() === 9) { + month = "Oktober"; + } else if (time.getMonth() === 10) { + month = "November"; + } else { + month = "Desember"; + } + + let year = time.getFullYear().toString(); + + fullDate = date + " " + month + " " + year; + + return fullDate; } } }; diff --git a/frontend/src/views/CourseEnrollment/CreateEditClass.vue b/frontend/src/views/CourseEnrollment/CreateEditClass.vue index 05c50fad88848394b85b78b412eff7af71478149..2de552b2ffaeeaa09bb5176c0850c10bc381b059 100644 --- a/frontend/src/views/CourseEnrollment/CreateEditClass.vue +++ b/frontend/src/views/CourseEnrollment/CreateEditClass.vue @@ -224,10 +224,84 @@ class="mt-2" type="text" placeholder="Contoh: 40" - v-model="classList[index].capacity" + v-model="singleClass.capacity" /> </div> </div> + <!-- Set Schedule --> + <!-- @TODO: Modify here --> + <div class="col-12 mt-2"> + <label>Jadwal Perkuliahan</label> + <div + class="row" + v-for="(schedule, childIndex) in singleClass.schedules" + :key="schedule.day" + > + <div class="col-md-4"> + <select + class="form-select mt-2 w-100" + aria-label="Hari" + v-model="schedule.day" + > + <option value="-" selected disabled hidden + >- Hari -</option + > + <option value="MONDAY">Senin</option> + <option value="TUESDAY">Selasa</option> + <option value="WEDNESDAY">Rabu</option> + <option value="THURSDAY">Kamis</option> + <option value="FRIDAY">Jumat</option> + <option value="SATURDAY">Sabtu</option> + <option value="SUNDAY">Minggu</option> + </select> + </div> + <div class="col-md-4"> + <vue-timepicker + class="mt-2" + placeholder="Jam mulai" + v-model="schedule.start" + :modelValue="schedule.start" + @update:modelValue=" + updateClassStartTime( + schedule.start, + index, + childIndex + ) + " + close-on-complete + input-width="100%" + ></vue-timepicker> + </div> + <div class="col-md-4"> + <vue-timepicker + class="mt-2" + placeholder="Jam akhir" + v-model="schedule.end" + :modelValue="schedule.end" + @update:modelValue=" + updateClassEndTime( + schedule.end, + index, + childIndex + ) + " + close-on-complete + input-width="100%" + ></vue-timepicker> + </div> + </div> + <div class="col-12 d-flex justify-content-end"> + <button + type="button" + class="btn px-0 pt-2" + @click="addNewSchedule(index)" + > + <p class="text-blue-six"> + + Tambah Jadwal + </p> + </button> + </div> + </div> </div> <div class="row"> <!-- Button Tambah Kelas --> @@ -290,11 +364,14 @@ import VPagination from "vue3-pagination"; import "vue3-pagination/dist/vue3-pagination.css"; import { Modal } from "bootstrap"; import axios from "axios"; +import VueTimepicker from "vue3-timepicker"; +import "vue3-timepicker/dist/VueTimepicker.css"; export default { components: { Sidebar, - VPagination + VPagination, + VueTimepicker }, name: "CreateEditClass", data() { @@ -367,7 +444,8 @@ export default { (await this.createNewClass( this.selectedCourseId, this.classList[i].lecturerId, - this.classList[i].capacity + this.classList[i].capacity, + this.classList[i].schedules )); } if (count == this.classList.length - this.newClassStartIndex) { @@ -381,9 +459,29 @@ export default { addNewClass() { this.classList.push({ capacity: null, - lecturerId: "" + lecturerId: "", + schedules: [ + { + day: "-", + start: null, + end: null + } + ] + }); + }, + addNewSchedule(classIndex) { + this.classList[classIndex].schedules.push({ + day: "-", + start: null, + end: null }); }, + updateClassStartTime(time, classIndex, scheduleIndex) { + this.classList[classIndex].schedules[scheduleIndex].start = time + ":00"; + }, + updateClassEndTime(time, classIndex, scheduleIndex) { + this.classList[classIndex].schedules[scheduleIndex].end = time + ":00"; + }, getAllCourses() { axios .get(process.env.VUE_APP_API_ENDPOINT + "/course") @@ -411,7 +509,10 @@ export default { // Select Class related to course for (let i = 0; i < response.data.length; i++) { if (response.data[i].courseId === idCourse) { - this.classList.push(response.data[i]); + let singleClass = response.data[i]; + singleClass.schedules = + response.data[i].courseClassDefaultSchedules; + this.classList.push(singleClass); this.newClassStartIndex = this.classList.length; } } @@ -465,16 +566,18 @@ export default { this.showedCourses = this.filteredCoursesList.slice(minIndex, maxIndex); this.startIndex = minIndex; }, - async createNewClass(courseId, lecturerId, capacity) { + async createNewClass(courseId, lecturerId, capacity, schedules) { let isSuccess = 0; + const newClass = { courseId: courseId, lecturerId: lecturerId, capacity: capacity, participantCount: 0, - semester: 2, + semester: 1, status: "OPEN", - startYear: 2020 + startYear: 2021, + courseClassDefaultSchedules: schedules }; await axios .post(process.env.VUE_APP_API_ENDPOINT + "/course-class", newClass)